Browse Source

scriptwrap fixes

master
Julian Noble 4 months ago
parent
commit
ba5a381c07
  1. 261
      src/bootsupport/modules/punk/mix/commandset/scriptwrap-0.1.0.tm
  2. 261
      src/modules/punk/mix/commandset/scriptwrap-999999.0a1.0.tm
  3. 208
      src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd
  4. 346
      src/modules/punk/mix/templates/utility/scriptappwrappers/multishell1.cmd
  5. 240
      src/modules/punk/mix/templates/utility/scriptappwrappers/multishell2.cmd
  6. 680
      src/modules/punk/mix/templates/utility/scriptappwrappers/multishell3.cmd
  7. 1
      src/modules/punk/pdf-999999.0a1.0.tm
  8. 29
      src/project_layouts/custom/_project/punk.basic/src/make.tcl
  9. 261
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/scriptwrap-0.1.0.tm
  10. 29
      src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl
  11. 261
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/scriptwrap-0.1.0.tm
  12. 29
      src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl
  13. 33
      src/scriptapps/bits.pl
  14. 9
      src/scriptapps/bits.ps1
  15. 1
      src/scriptapps/bits.sh
  16. 30
      src/scriptapps/bits_wrap.toml
  17. 10
      src/scriptapps/example_wrap.toml
  18. 50
      src/scriptapps/fetchruntime.bash
  19. 25
      src/scriptapps/fetchruntime.ps1
  20. 42
      src/scriptapps/fetchruntime.tcl
  21. 22
      src/scriptapps/fetchruntime_old.ps1
  22. 21
      src/scriptapps/fetchruntime_wrap.toml
  23. 261
      src/vfs/_vfscommon.vfs/modules/punk/mix/commandset/scriptwrap-0.1.0.tm
  24. 208
      src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd
  25. 346
      src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell1.cmd
  26. 240
      src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell2.cmd
  27. 680
      src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell3.cmd
  28. 1
      src/vfs/_vfscommon.vfs/modules/punk/pdf-0.1.0.tm

261
src/bootsupport/modules/punk/mix/commandset/scriptwrap-0.1.0.tm

@ -53,6 +53,7 @@ package require punk::args
package require punk::mix
package require punk::mix::base
package require punk::fileline
package require punk::ansi
#*** !doctools
@ -78,6 +79,7 @@ namespace eval punk::mix::commandset::scriptwrap {
#[list_begin definitions]
namespace export {[a-z]*}
namespace import ::punk::ansi::a ::punk::ansi::a+
namespace eval fileline {
namespace import ::punk::fileline::lib::*
@ -807,7 +809,7 @@ namespace eval punk::mix::commandset::scriptwrap {
}
return $result
}
#specific filepath to just wrap one script at the xxx-pre-launch-suprocess site
#specific filepath to just wrap one script at the xxx-payload site
#scriptset name to substitute multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
#set usage ""
#append usage "Use directly with the script file to wrap, or supply the name of a scriptset" \n
@ -830,6 +832,17 @@ namespace eval punk::mix::commandset::scriptwrap {
if {[tomlish::dict::path::exists $tomldict {.application.template}]} {
dict set resultd template [tomlish::dict::path::get $tomldict {.application.template.value}]
}
if {[tomlish::dict::path::exists $tomldict {.application.as_admin}]} {
set val [tomlish::dict::path::get $tomldict {.application.as_admin.value}]
if {$val && 1} {
dict set resultd as_admin 1
} else {
dict set resultd as_admin 0
}
} else {
dict set resultd as_admin 0
}
set scripts [list]
if {[tomlish::dict::path::exists $tomldict {.application.scripts.value}]} {
set arrvalues [tomlish::dict::path::get $tomldict {.application.scripts.value}]
@ -872,6 +885,60 @@ namespace eval punk::mix::commandset::scriptwrap {
return $resultd
}
proc _default_configd {} {
set configd [dict create]
dict set configd template ""
dict set configd as_admin 0
dict set configd scripts [list]
dict set configd default_outputfile ""
dict set configd default_nextshellpath ""
dict set configd default_nextshelltype "none"
foreach os {win32 dragonflybsd freebsd netbsd linux macosx other} {
dict set configd $os outputfile ""
dict set configd $os nextshellpath ""
dict set configd $os nextshelltype "none"
}
return $configd
}
proc _get_nextshell_script {configd} {
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________"
#delimeters
#: <<nextshell_start>>
#: <<nextshell_end>>
set script ""
dict for {k v} $configd {
if {[llength $v] % 2 == 0 && [dict exists $v nextshelltype]} {
set os $k
set n [expr {16 - [string length $os]}]
set _os [string repeat _ $n]
set path [dict get $v nextshellpath]
set n [expr {64 - [string length $path]}]
set _path [string repeat _ $n]
set type [dict get $v nextshelltype]
set n [expr {16 - [string length $type]}]
set _type [string repeat _ $n]
append script "@SET \"nextshellpath\[$os$_os\]=$path$_path\"" \n
append script "@SET \"nextshelltype\[$os$_os\]=$type$_type\"" \n
}
}
set script [string trimright $script \n]
return $script
}
punk::args::define {
@id -id ::punk::mix::commandset::scriptwrap::multishell
@cmd -name punk::mix::commandset::scriptwrap::multishell\
@ -914,22 +981,22 @@ namespace eval punk::mix::commandset::scriptwrap {
-returnextra -type boolean -default 0
@values -minvalues 0 -maxvalues 0
}
#: <nextshell>
#@SET "nextshellpath[win32___________]=tclsh___________________________"
#: <<nextshell_start>>
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________"
#: </nextshell>
#: <<nextshell_end>>
proc multishell {args} {
set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell]
lassign [dict values $argd] leaders opts values received
@ -947,7 +1014,7 @@ namespace eval punk::mix::commandset::scriptwrap {
set startdir [pwd]
set allowed_extensions [list tcl ps1 sh bash pl]
#TODO - distinct sections for sh vs bash? needs experiments..
#for now we use shell-pre-launch-subprocess etc
#for now we use shell-payload etc
#set extension_langs [list tcl tcl ps1 powershell sh sh bash bash pl perl]
set extension_langs [list tcl tcl ps1 powershell sh shell bash shell pl perl]
@ -990,13 +1057,15 @@ namespace eval punk::mix::commandset::scriptwrap {
}
set list_input_files [list]
set has_config 0
set configd [dict create]
if {$scriptset ne ""} {
puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'"
#.toml file may or may not exist
if {[file exists ${scriptset}_wrap.toml]} {
puts stdout "Loading configuration from $scriptdir/${scriptset}_wrap.toml"
puts stdout "[a bold green]Loading configuration from $scriptdir/${scriptset}_wrap.toml[a]"
set configd [_read_scriptset_wrap_tomlfile $scriptdir/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts {
@ -1008,6 +1077,7 @@ namespace eval punk::mix::commandset::scriptwrap {
return false
}
} else {
set configd [_default_configd]
puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml"
puts stdout "Will look for the following scripts in $scriptdir"
foreach e $allowed_extensions {
@ -1018,12 +1088,17 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptdir/$scriptset.$e
}
}
dict set configd scripts $list_input_files
}
} else {
set configd [_default_configd]
#expect a single script
if {[file exists $specified_path]} {
lappend list_input_files $specified_path
}
dict set configd scripts [lmap f $list_input_files {file tail $f}]
set ftail [file tail $specified_path]
dict set configd default_outputfile [file rootname $ftail]
}
set found_script [expr {[llength $list_input_files] > 0}]
@ -1061,8 +1136,9 @@ namespace eval punk::mix::commandset::scriptwrap {
if {$scriptset ne ""} {
#.toml file may or may not exist
if {[file exists $scriptroot/${scriptset}_wrap.toml]} {
puts stdout "Loading configuration from $scriptroot/${scriptset}_wrap.toml"
puts stdout "[a green]Loading configuration from $scriptroot/${scriptset}_wrap.toml[a]"
set configd [_read_scriptset_wrap_tomlfile $scriptroot/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts {
@ -1084,6 +1160,8 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptroot/$scriptset.$e
}
}
set configd [_default_configd]
dict set configd scripts [lmap f $list_input_files {file tail $f}]
}
} else {
#expect a single script
@ -1094,6 +1172,8 @@ namespace eval punk::mix::commandset::scriptwrap {
}
lappend list_input_files $scriptroot/$filepath_or_scriptset
}
set configd [_default_configd]
dict set configd scripts [lmap f $list_input_files {file tail $f}]
}
set found_script [expr {[llength $list_input_files] > 0}]
@ -1111,7 +1191,7 @@ namespace eval punk::mix::commandset::scriptwrap {
}
#assertion - customwrapper_folder var exists - but might be empty
if {[dict exists $configd template]} {
if {[dict get $configd template] ne ""} {
set templatename [dict get $configd template]
} else {
if {$opt_template eq "\uFFFF"} {
@ -1119,6 +1199,7 @@ namespace eval punk::mix::commandset::scriptwrap {
} else {
set templatename $opt_template
}
dict set configd template $templatename
}
set templatename_root [file rootname [file tail $templatename]]
@ -1199,7 +1280,6 @@ namespace eval punk::mix::commandset::scriptwrap {
#todo
#output_file extension may also depend on the template being used.. and/or the <scriptset>_wrap.toml config
if {[dict size $configd]} {
package require platform
set thisplatform [string tolower [platform::identify]]
set ptype [lindex [split $thisplatform -] 0]
@ -1209,11 +1289,17 @@ namespace eval punk::mix::commandset::scriptwrap {
set ptype other
}
}
if {$has_config} {
set out [dict get $configd $ptype outputfile]
if {[string trim $out] ne ""} {
set output_file [file join $output_folder $out]
} else {
#can be empty for this os if configured that way in xxx_wrap.toml
set output_file ""
}
} else {
#no _wrap.toml file available
if {$::tcl_platform(platform) eq "windows"} {
if {$ptype eq "win32"} {
set output_extension .cmd
} else {
set output_extension .sh
@ -1224,6 +1310,11 @@ namespace eval punk::mix::commandset::scriptwrap {
set infile [lindex $list_input_files 0]
set output_file [file join $output_folder [file rootname [file tail $infile]]$output_extension]
}
dict set configd $ptype outputfile [file tail $output_file]
}
if {$output_file eq ""} {
puts stderr "No output file configured for platform $ptype"
return
}
@ -1236,7 +1327,7 @@ namespace eval punk::mix::commandset::scriptwrap {
puts stdout "wrap_in_multishell: target file $output_file already exists. File size: [$objFile_existing chunklen] Line count: [$objFile_existing linecount]"
if {!$opt_force} {
if {$opt_askme} {
set answer [util::askuser "Do you want to overwrite $output_file? Y|N"]
set answer [util::askuser "Do you want to [a bold white Red]overwrite[a] [a bold red]$output_file[a]? Y|N"]
if {[string tolower $answer] ne "y"} {
puts stderr "aborting due to user response '$answer' (required Y or y to proceed) use -force 1 or -askme 0 to avoid prompts."
$objFile_existing destroy
@ -1264,36 +1355,33 @@ namespace eval punk::mix::commandset::scriptwrap {
puts stdout $ln
}
puts stdout "-----------------------------------------------\n"
#foreach ln $template_lines {
#}
if {[llength $list_input_files] > 1} {
#todo
puts stderr "Sorry - only single input file supported. Supply a file extension or use a <scriptset>_wrap.toml config with a single input file for now - implementation incomplete"
return false
}
#todo - split template at each <ext-*-subprocess> etc marker and build a dict of parts
#if {[llength $list_input_files] > 1} {
# #todo
# puts stderr "Sorry - only single input file supported. Supply a file extension or use a <scriptset>_wrap.toml config with a single input file for now - implementation incomplete"
# return false
#}
#hack - process one input
set filepath [lindex $list_input_files 0]
set lang_data [dict create]
foreach filepath $list_input_files {
set script_ext [string trim [file extension $filepath] .]
set lang [dict get $extension_langs [string tolower $script_ext]]
set fdscript [open $filepath r]
fconfigure $fdscript -translation binary
set script_data [read $fdscript]
close $fdscript
puts stdout "Read [string length $script_data] bytes of template data.."
puts stdout "Read [string length $script_data] bytes of template data for lang: $lang"
set script_lines [split $script_data \n]
puts stdout "Displaying first 3 lines of your script between dashed lines..."
dict set lang_data $lang $script_lines
puts stdout "Displaying first 3 lines of your script [file tail $filepath] between dashed lines..."
puts stdout "[a green]$filepath[a]"
puts stdout "-----------------------------------------------"
foreach ln [lrange $script_lines 0 3] {
puts stdout $ln
}
puts stdout "-----------------------------------------------\n"
puts stdout "Target for above script data is '$output_file'"
set script_ext [string trim [file extension $filepath] .]
set lang [dict get $extension_langs [string tolower $script_ext]]
puts stdout "Language of script being wrapped is $lang"
puts stdout "Target for script data is '$output_file'"
puts stdout "Language of script being wrapped is [a bold yellow]$lang[a]"
if {$opt_askme} {
set answer [util::askuser "Does this look correct? Y|N"]
if {[string tolower $answer] ne "y"} {
@ -1301,55 +1389,88 @@ namespace eval punk::mix::commandset::scriptwrap {
return
}
}
}
set start_idx 0
set end_idx 0
set line_idx 0
set existing_payload [list]
set template_ranges [list]
set data_items [list]
set trange [list 0]
set line_idx -1
set opentag ""
foreach ln $template_lines {
if {[string match "#<$lang-pre-launch-subprocess>*" $ln]} {
set start_idx $line_idx
} elseif {[string match "#</$lang-pre-launch-subprocess>*" $ln]} {
set end_idx $line_idx
break
} elseif {$start_idx > 0} {
if {$end_idx > 0} {
lappend existing_payload [string trim $ln]
incr line_idx
if {$opentag eq ""} {
if {[string match ": <<asadmin_start>>*" $ln]} {
set opentag asadmin
lset trange 1 $line_idx ;#include tag in template
lappend template_ranges $trange
set trange [list $line_idx]
set asadmin [dict get $configd as_admin]
set scr "@SET \"asadmin=$asadmin\""
lappend data_items $scr
} elseif {[string match ": <<nextshell_start>>*" $ln]} {
set opentag nextshell
lset trange 1 $line_idx
lappend template_ranges $trange
set trange [list $line_idx]
set nextshell_script [_get_nextshell_script $configd]
#puts stderr "-------------------"
#puts stderr "$nextshell_script"
lappend data_items $nextshell_script
} elseif {[string match "#<*-payload>*" $ln]} {
regexp {#<(.*)-payload>.*} $ln _ lang
if {[dict exists $lang_data $lang]} {
set script_lines [dict get $lang_data $lang]
set opentag payload-$lang
lset trange 1 $line_idx
lappend template_ranges $trange
lappend data_items [join $script_lines \n]
}
}
} else {
switch -- [string range $opentag 0 6] {
asadmin {
if {[string match ": <<asadmin_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
incr line_idx
}
if {($start_idx == 0) || ($end_idx == 0)} {
error "wrap_in_multishell was unable to find payload area in template marked with #<$lang-pre-launch-subprocess> and #</$lang-pre-launch-subprocess> on separate lines"
nextshe {
if {[string match ": <<nextshell_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
set existing_string [join $existing_payload \n]
if {[string length [string trim $existing_string]]} {
puts stdout "EXISTING <$lang-pre-launch-subprocess> PAYLOAD!!"
puts stdout "-----------------------------------------------\n"
puts stdout $existing_string
puts stdout "-----------------------------------------------\n"
error "wrap_in_multishell found existing payload for language $lang ... aborting."
#todo - allow overwrite only in files outside of punkshell distribution?
if 0 {
puts stderr "Found existing $lang payload.. overwrite?"
if {$opt_askme} {
set answer [util::askuser "Are you sure you want to replace the $lang payload shown above? Y|N"]
if {[string tolower $answer] ne "y"} {
puts stderr "mix new aborting due to user response '$answer' (required Y|y to proceed) use -askme 0 to avoid prompts."
return
}
payload {
set lang [string range $opentag 8 end] ;#payload-xxx
if {[string match "#</$lang-payload>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
}
}
}
if {$opentag eq ""} {
lset trange 1 end
lappend template_ranges $trange
} else {
error "multishell - unable to find closing tag for '$opentag'"
}
set newscript ""
foreach trange $template_ranges item $data_items {
append newscript [join [lrange $template_lines {*}$trange] \n]
if {$item ne ""} {
append newscript \n $item \n
} else {
append newscript \n
}
}
set tpl_head_lines [lrange $template_lines 0 $start_idx] ;#include tag line
set tpl_tail_lines [lrange $template_lines $end_idx end]
set newscript [join $tpl_head_lines \n]\n[join $script_lines \n]\n[join $tpl_tail_lines \n]
puts stdout "New script is [string length $newscript] bytes"
puts stdout $newscript
set fdtarget [open $output_file w]
fconfigure $fdtarget -translation binary
puts -nonewline $fdtarget $newscript

261
src/modules/punk/mix/commandset/scriptwrap-999999.0a1.0.tm

@ -53,6 +53,7 @@ package require punk::args
package require punk::mix
package require punk::mix::base
package require punk::fileline
package require punk::ansi
#*** !doctools
@ -78,6 +79,7 @@ namespace eval punk::mix::commandset::scriptwrap {
#[list_begin definitions]
namespace export {[a-z]*}
namespace import ::punk::ansi::a ::punk::ansi::a+
namespace eval fileline {
namespace import ::punk::fileline::lib::*
@ -807,7 +809,7 @@ namespace eval punk::mix::commandset::scriptwrap {
}
return $result
}
#specific filepath to just wrap one script at the xxx-pre-launch-suprocess site
#specific filepath to just wrap one script at the xxx-payload site
#scriptset name to substitute multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
#set usage ""
#append usage "Use directly with the script file to wrap, or supply the name of a scriptset" \n
@ -830,6 +832,17 @@ namespace eval punk::mix::commandset::scriptwrap {
if {[tomlish::dict::path::exists $tomldict {.application.template}]} {
dict set resultd template [tomlish::dict::path::get $tomldict {.application.template.value}]
}
if {[tomlish::dict::path::exists $tomldict {.application.as_admin}]} {
set val [tomlish::dict::path::get $tomldict {.application.as_admin.value}]
if {$val && 1} {
dict set resultd as_admin 1
} else {
dict set resultd as_admin 0
}
} else {
dict set resultd as_admin 0
}
set scripts [list]
if {[tomlish::dict::path::exists $tomldict {.application.scripts.value}]} {
set arrvalues [tomlish::dict::path::get $tomldict {.application.scripts.value}]
@ -872,6 +885,60 @@ namespace eval punk::mix::commandset::scriptwrap {
return $resultd
}
proc _default_configd {} {
set configd [dict create]
dict set configd template ""
dict set configd as_admin 0
dict set configd scripts [list]
dict set configd default_outputfile ""
dict set configd default_nextshellpath ""
dict set configd default_nextshelltype "none"
foreach os {win32 dragonflybsd freebsd netbsd linux macosx other} {
dict set configd $os outputfile ""
dict set configd $os nextshellpath ""
dict set configd $os nextshelltype "none"
}
return $configd
}
proc _get_nextshell_script {configd} {
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________"
#delimeters
#: <<nextshell_start>>
#: <<nextshell_end>>
set script ""
dict for {k v} $configd {
if {[llength $v] % 2 == 0 && [dict exists $v nextshelltype]} {
set os $k
set n [expr {16 - [string length $os]}]
set _os [string repeat _ $n]
set path [dict get $v nextshellpath]
set n [expr {64 - [string length $path]}]
set _path [string repeat _ $n]
set type [dict get $v nextshelltype]
set n [expr {16 - [string length $type]}]
set _type [string repeat _ $n]
append script "@SET \"nextshellpath\[$os$_os\]=$path$_path\"" \n
append script "@SET \"nextshelltype\[$os$_os\]=$type$_type\"" \n
}
}
set script [string trimright $script \n]
return $script
}
punk::args::define {
@id -id ::punk::mix::commandset::scriptwrap::multishell
@cmd -name punk::mix::commandset::scriptwrap::multishell\
@ -914,22 +981,22 @@ namespace eval punk::mix::commandset::scriptwrap {
-returnextra -type boolean -default 0
@values -minvalues 0 -maxvalues 0
}
#: <nextshell>
#@SET "nextshellpath[win32___________]=tclsh___________________________"
#: <<nextshell_start>>
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________"
#: </nextshell>
#: <<nextshell_end>>
proc multishell {args} {
set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell]
lassign [dict values $argd] leaders opts values received
@ -947,7 +1014,7 @@ namespace eval punk::mix::commandset::scriptwrap {
set startdir [pwd]
set allowed_extensions [list tcl ps1 sh bash pl]
#TODO - distinct sections for sh vs bash? needs experiments..
#for now we use shell-pre-launch-subprocess etc
#for now we use shell-payload etc
#set extension_langs [list tcl tcl ps1 powershell sh sh bash bash pl perl]
set extension_langs [list tcl tcl ps1 powershell sh shell bash shell pl perl]
@ -990,13 +1057,15 @@ namespace eval punk::mix::commandset::scriptwrap {
}
set list_input_files [list]
set has_config 0
set configd [dict create]
if {$scriptset ne ""} {
puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'"
#.toml file may or may not exist
if {[file exists ${scriptset}_wrap.toml]} {
puts stdout "Loading configuration from $scriptdir/${scriptset}_wrap.toml"
puts stdout "[a bold green]Loading configuration from $scriptdir/${scriptset}_wrap.toml[a]"
set configd [_read_scriptset_wrap_tomlfile $scriptdir/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts {
@ -1008,6 +1077,7 @@ namespace eval punk::mix::commandset::scriptwrap {
return false
}
} else {
set configd [_default_configd]
puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml"
puts stdout "Will look for the following scripts in $scriptdir"
foreach e $allowed_extensions {
@ -1018,12 +1088,17 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptdir/$scriptset.$e
}
}
dict set configd scripts $list_input_files
}
} else {
set configd [_default_configd]
#expect a single script
if {[file exists $specified_path]} {
lappend list_input_files $specified_path
}
dict set configd scripts [lmap f $list_input_files {file tail $f}]
set ftail [file tail $specified_path]
dict set configd default_outputfile [file rootname $ftail]
}
set found_script [expr {[llength $list_input_files] > 0}]
@ -1061,8 +1136,9 @@ namespace eval punk::mix::commandset::scriptwrap {
if {$scriptset ne ""} {
#.toml file may or may not exist
if {[file exists $scriptroot/${scriptset}_wrap.toml]} {
puts stdout "Loading configuration from $scriptroot/${scriptset}_wrap.toml"
puts stdout "[a green]Loading configuration from $scriptroot/${scriptset}_wrap.toml[a]"
set configd [_read_scriptset_wrap_tomlfile $scriptroot/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts {
@ -1084,6 +1160,8 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptroot/$scriptset.$e
}
}
set configd [_default_configd]
dict set configd scripts [lmap f $list_input_files {file tail $f}]
}
} else {
#expect a single script
@ -1094,6 +1172,8 @@ namespace eval punk::mix::commandset::scriptwrap {
}
lappend list_input_files $scriptroot/$filepath_or_scriptset
}
set configd [_default_configd]
dict set configd scripts [lmap f $list_input_files {file tail $f}]
}
set found_script [expr {[llength $list_input_files] > 0}]
@ -1111,7 +1191,7 @@ namespace eval punk::mix::commandset::scriptwrap {
}
#assertion - customwrapper_folder var exists - but might be empty
if {[dict exists $configd template]} {
if {[dict get $configd template] ne ""} {
set templatename [dict get $configd template]
} else {
if {$opt_template eq "\uFFFF"} {
@ -1119,6 +1199,7 @@ namespace eval punk::mix::commandset::scriptwrap {
} else {
set templatename $opt_template
}
dict set configd template $templatename
}
set templatename_root [file rootname [file tail $templatename]]
@ -1199,7 +1280,6 @@ namespace eval punk::mix::commandset::scriptwrap {
#todo
#output_file extension may also depend on the template being used.. and/or the <scriptset>_wrap.toml config
if {[dict size $configd]} {
package require platform
set thisplatform [string tolower [platform::identify]]
set ptype [lindex [split $thisplatform -] 0]
@ -1209,11 +1289,17 @@ namespace eval punk::mix::commandset::scriptwrap {
set ptype other
}
}
if {$has_config} {
set out [dict get $configd $ptype outputfile]
if {[string trim $out] ne ""} {
set output_file [file join $output_folder $out]
} else {
#can be empty for this os if configured that way in xxx_wrap.toml
set output_file ""
}
} else {
#no _wrap.toml file available
if {$::tcl_platform(platform) eq "windows"} {
if {$ptype eq "win32"} {
set output_extension .cmd
} else {
set output_extension .sh
@ -1224,6 +1310,11 @@ namespace eval punk::mix::commandset::scriptwrap {
set infile [lindex $list_input_files 0]
set output_file [file join $output_folder [file rootname [file tail $infile]]$output_extension]
}
dict set configd $ptype outputfile [file tail $output_file]
}
if {$output_file eq ""} {
puts stderr "No output file configured for platform $ptype"
return
}
@ -1236,7 +1327,7 @@ namespace eval punk::mix::commandset::scriptwrap {
puts stdout "wrap_in_multishell: target file $output_file already exists. File size: [$objFile_existing chunklen] Line count: [$objFile_existing linecount]"
if {!$opt_force} {
if {$opt_askme} {
set answer [util::askuser "Do you want to overwrite $output_file? Y|N"]
set answer [util::askuser "Do you want to [a bold white Red]overwrite[a] [a bold red]$output_file[a]? Y|N"]
if {[string tolower $answer] ne "y"} {
puts stderr "aborting due to user response '$answer' (required Y or y to proceed) use -force 1 or -askme 0 to avoid prompts."
$objFile_existing destroy
@ -1264,36 +1355,33 @@ namespace eval punk::mix::commandset::scriptwrap {
puts stdout $ln
}
puts stdout "-----------------------------------------------\n"
#foreach ln $template_lines {
#}
if {[llength $list_input_files] > 1} {
#todo
puts stderr "Sorry - only single input file supported. Supply a file extension or use a <scriptset>_wrap.toml config with a single input file for now - implementation incomplete"
return false
}
#todo - split template at each <ext-*-subprocess> etc marker and build a dict of parts
#if {[llength $list_input_files] > 1} {
# #todo
# puts stderr "Sorry - only single input file supported. Supply a file extension or use a <scriptset>_wrap.toml config with a single input file for now - implementation incomplete"
# return false
#}
#hack - process one input
set filepath [lindex $list_input_files 0]
set lang_data [dict create]
foreach filepath $list_input_files {
set script_ext [string trim [file extension $filepath] .]
set lang [dict get $extension_langs [string tolower $script_ext]]
set fdscript [open $filepath r]
fconfigure $fdscript -translation binary
set script_data [read $fdscript]
close $fdscript
puts stdout "Read [string length $script_data] bytes of template data.."
puts stdout "Read [string length $script_data] bytes of template data for lang: $lang"
set script_lines [split $script_data \n]
puts stdout "Displaying first 3 lines of your script between dashed lines..."
dict set lang_data $lang $script_lines
puts stdout "Displaying first 3 lines of your script [file tail $filepath] between dashed lines..."
puts stdout "[a green]$filepath[a]"
puts stdout "-----------------------------------------------"
foreach ln [lrange $script_lines 0 3] {
puts stdout $ln
}
puts stdout "-----------------------------------------------\n"
puts stdout "Target for above script data is '$output_file'"
set script_ext [string trim [file extension $filepath] .]
set lang [dict get $extension_langs [string tolower $script_ext]]
puts stdout "Language of script being wrapped is $lang"
puts stdout "Target for script data is '$output_file'"
puts stdout "Language of script being wrapped is [a bold yellow]$lang[a]"
if {$opt_askme} {
set answer [util::askuser "Does this look correct? Y|N"]
if {[string tolower $answer] ne "y"} {
@ -1301,55 +1389,88 @@ namespace eval punk::mix::commandset::scriptwrap {
return
}
}
}
set start_idx 0
set end_idx 0
set line_idx 0
set existing_payload [list]
set template_ranges [list]
set data_items [list]
set trange [list 0]
set line_idx -1
set opentag ""
foreach ln $template_lines {
if {[string match "#<$lang-pre-launch-subprocess>*" $ln]} {
set start_idx $line_idx
} elseif {[string match "#</$lang-pre-launch-subprocess>*" $ln]} {
set end_idx $line_idx
break
} elseif {$start_idx > 0} {
if {$end_idx > 0} {
lappend existing_payload [string trim $ln]
incr line_idx
if {$opentag eq ""} {
if {[string match ": <<asadmin_start>>*" $ln]} {
set opentag asadmin
lset trange 1 $line_idx ;#include tag in template
lappend template_ranges $trange
set trange [list $line_idx]
set asadmin [dict get $configd as_admin]
set scr "@SET \"asadmin=$asadmin\""
lappend data_items $scr
} elseif {[string match ": <<nextshell_start>>*" $ln]} {
set opentag nextshell
lset trange 1 $line_idx
lappend template_ranges $trange
set trange [list $line_idx]
set nextshell_script [_get_nextshell_script $configd]
#puts stderr "-------------------"
#puts stderr "$nextshell_script"
lappend data_items $nextshell_script
} elseif {[string match "#<*-payload>*" $ln]} {
regexp {#<(.*)-payload>.*} $ln _ lang
if {[dict exists $lang_data $lang]} {
set script_lines [dict get $lang_data $lang]
set opentag payload-$lang
lset trange 1 $line_idx
lappend template_ranges $trange
lappend data_items [join $script_lines \n]
}
}
} else {
switch -- [string range $opentag 0 6] {
asadmin {
if {[string match ": <<asadmin_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
incr line_idx
}
if {($start_idx == 0) || ($end_idx == 0)} {
error "wrap_in_multishell was unable to find payload area in template marked with #<$lang-pre-launch-subprocess> and #</$lang-pre-launch-subprocess> on separate lines"
nextshe {
if {[string match ": <<nextshell_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
set existing_string [join $existing_payload \n]
if {[string length [string trim $existing_string]]} {
puts stdout "EXISTING <$lang-pre-launch-subprocess> PAYLOAD!!"
puts stdout "-----------------------------------------------\n"
puts stdout $existing_string
puts stdout "-----------------------------------------------\n"
error "wrap_in_multishell found existing payload for language $lang ... aborting."
#todo - allow overwrite only in files outside of punkshell distribution?
if 0 {
puts stderr "Found existing $lang payload.. overwrite?"
if {$opt_askme} {
set answer [util::askuser "Are you sure you want to replace the $lang payload shown above? Y|N"]
if {[string tolower $answer] ne "y"} {
puts stderr "mix new aborting due to user response '$answer' (required Y|y to proceed) use -askme 0 to avoid prompts."
return
}
payload {
set lang [string range $opentag 8 end] ;#payload-xxx
if {[string match "#</$lang-payload>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
}
}
}
if {$opentag eq ""} {
lset trange 1 end
lappend template_ranges $trange
} else {
error "multishell - unable to find closing tag for '$opentag'"
}
set newscript ""
foreach trange $template_ranges item $data_items {
append newscript [join [lrange $template_lines {*}$trange] \n]
if {$item ne ""} {
append newscript \n $item \n
} else {
append newscript \n
}
}
set tpl_head_lines [lrange $template_lines 0 $start_idx] ;#include tag line
set tpl_tail_lines [lrange $template_lines $end_idx end]
set newscript [join $tpl_head_lines \n]\n[join $script_lines \n]\n[join $tpl_tail_lines \n]
puts stdout "New script is [string length $newscript] bytes"
puts stdout $newscript
set fdtarget [open $output_file w]
fconfigure $fdtarget -translation binary
puts -nonewline $fdtarget $newscript

208
src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd

@ -1,5 +1,5 @@
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" +
@ -16,41 +16,41 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@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 On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used)
@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM 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 <inputfilepath> -outputfolder <folderpath>
@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
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshelltypes= powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________"
@SET "validshelltypes= 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 32 chars needed for a target, it can still be done but overall script padding may need checking/adjusting
@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
: <nextshell>
@SET "nextshellpath[win32___________]=tclsh___________________________"
: <<nextshell_start>>
@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
@SET "nextshelltype[win32___________]=tcl_____________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[dragonflybsd____]=tcl_____________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[freebsd_________]=tcl_____________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[netbsd__________]=tcl_____________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[linux___________]=tcl_____________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[macosx__________]=tcl_____________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[other___________]=tcl_____________"
: </nextshell>
: <<nextshell_end>>
@rem asadmin is for automatic elevation to administrator. Separate window will be created (seems unavoidable with current elevation mechanism) and user will still get security prompt (probably reasonable).
: <asadmin>
: <<asadmin_start>>
@SET "asadmin=0"
: </asadmin>
: <<asadmin_end>>
@REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM @SET "selected_shelltype=%nextshelltype[win32___________]%"
@SET "selected_shelltype=%nextshelltype[win32___________]%"
@ -143,7 +143,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script in new windows due to administrator elevation
@ECHO Launching script in new window due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B
@ -175,8 +175,11 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$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%"=="powershell" (
REM pws vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time
@IF "!selected_shelltype_trimmed!"=="none" (
SET selected_shelltype_trimmed=powershell
)
@IF "!selected_shelltype_trimmed!"=="powershell" (
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; write-host "statusmessage: pwsh-found" >NUL
SET pwshtest_exitcode=!errorlevel!
@ -192,7 +195,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "%selected_shelltype_trimmed%"=="wslbash" (
IF "!selected_shelltype_trimmed!"=="wslbash" (
CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%"
%selected_shellpath_trimmed% "!wslpath!%fname%" %arglist%
@ -203,6 +206,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
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% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
) ELSE (
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
@ -383,14 +387,15 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@SET "rtrn=%~2"
@SET "string=%~1"
@SET "trimstring=%~1"
@REM trim up to 31 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:###=%
@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 "" (
@ -439,7 +444,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
# -- e.g tclsh filename.cmd
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
rename set ""; rename s set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
rename set ""; rename S set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@
namespace eval ::punk::multishell {
set last_script_root [file dirname [file normalize ${::argv0}/__]]
@ -473,6 +478,9 @@ namespace eval ::punk::multishell {
#puts "argv0 : $::argv0"
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-payload>
puts stderr "No tcl code for this script. Try another program such as perl or bash"
#</tcl-payload>
#<tcl-pre-launch-subprocess>
#</tcl-pre-launch-subprocess>
@ -502,8 +510,20 @@ if {[::punk::multishell::is_main]} {
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload
# end hide from unix shells \
HEREDOC1B_HIDE_FROM_BASH_AND_SH
# csh/tcsh/sh/bash use oldschool backticks and sed lowest common denominator \
echo "script: `echo $0 | sed 's/^-//'`"
# csh/tcsh/sh/bash use oldschool backticks and sed lowest common denominator \
echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
#csh/tcsh diversion \
test "$argv[*]" != "[*]" && ( /usr/bin/env bash $argv[*]; exit )
#other non-bash diversion \
test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && /usr/bin/env bash $0
#review \
test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && exit
# sh/bash \
shift && set -- "${@:1:$#-1}"
#echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'`
#------------------------------------------------------
# -- This if block only needed if Tcl didn't exit or return above.
if false==false # else {
@ -518,10 +538,80 @@ if false==false # else {
# -- if sh/bash scripting needs to run on windows too.
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
if [[ "$OSTYPE" == "linux"* ]]; then
os="linux"
elif [[ "$OSTYPE" == "darwin"* ]]; then
os="macosx"
elif [[ "$OSTYPE" == "freebsd"* ]]; then
os="freebsd"
elif [[ "$OSTYPE" == "dragonflybsd"* ]]; then
os="dragonflybsd"
elif [[ "$OSTYPE" == "netbsd"* ]]; then
os="netbsd"
elif [[ "$OSTYPE" == "win32" ]]; then
os="win32"
elif [[ "$OSTYPE" == "msys" ]]; then
echo 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}}"
else
#os="$OSTYPE"
os="other"
fi
echo ostype: $OSTYPE
shellconfigline=$( sed -n "/: <<nextshell_start>>/{:a;n;/: <<nextshell_end>>/q;p;ba}" "$0" | grep $os)
#echo $shellconfigline;
if [[ $shellconfigline == *"nextshelltype"* ]]; then
echo "found config for os $os"
split1="${shellconfigline#*=}" #remove everything through the first '='
#echo "split1: $split1"
pathraw="${split1%%\"*}" #take everything before the quote - use %% to get longest match
pathraw="${pathraw//\"/}" #remove quote
nextshellpath="${pathraw/%_*/}" #remove trailing underscores (% = must match at end)
#echo "nextshellpath: $nextshellpath"
split2="${split1#*=}"
#echo "split2: $split2"
split2="${split2//\"/}"
nextshelltype="${split2/%_*/}"
echo "nextshelltype: $nextshelltype"
else
echo "unable to find config for os $os"
echo "shellconfigline: $shellconfigline"
nextshellpath=""
nextshelltype=""
fi
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 $nextshellpath on "$0"
#/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"
#<shell-payload>
echo "No bash code for this script. Try another program such as perl or tcl" >&2
#</shell-payload>
#<shell-pre-launch-subprocess>
#</shell-pre-launch-subprocess>
@ -531,8 +621,8 @@ exitcode=0
#-- 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=$?
#/usr/bin/env tclsh "$0" "$@"
#exitcode=$?
#echo "sh/bash reporting tcl exitcode: ${exitcode}"
#-- override exitcode example
#exit 66
@ -558,8 +648,18 @@ exit ${exitcode}
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
=cut
#!/user/bin/perl
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
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();
@ -571,13 +671,15 @@ my $exit_code = 0;
my $scriptname = $0;
print "perl $scriptname\n";
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
}
#<perl-payload>
print STDERR "No perl code for this script. Try another program such as tcl or bash";
#</perl-payload>
#<perl-pre-launch-subprocess>
#</perl-pre-launch-subprocess>
@ -585,7 +687,7 @@ foreach my $a(@ARGV) {
# -- --- --- --- --- --- --- ---
#<perl-launch-subprocess>
$exit_code=system("tclsh", $scriptname, @ARGV);
#$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess>
# -- --- --- --- --- --- --- ---
@ -680,12 +782,34 @@ function GetDynamicParamDictionary {
# }
#}
#psmain @args
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host
#"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
$startTag = ": <<asadmin_start>>"
$endTag = ": <<asadmin_end>>"
$fileContent = Get-Content $scriptname -Raw
$pattern = "(?s)$startTag(.*?)$endTag"
$matches = [regex]::Matches($fileContent,$pattern)
$admininfo = $matches[0].Groups[1].Value
$asadmin = 0
if ($matches.count) {
$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
Start-Process -FilePath "pwsh.exe" -ArgumentList "-NoProfile -NoExit -ExecutionPolicy Bypass -File $($MyInvocation.MyCommand.Path)" -Wait -Verb RunAs
Exit # Exit the current non-elevated process
}
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#<powershell-payload>
Write-Error "No powershell code for this script. Try another program such as perl, tcl or bash"
#</powershell-payload>
#<powershell-pre-launch-subprocess>
#</powershell-pre-launch-subprocess>
@ -693,7 +817,7 @@ function GetDynamicParamDictionary {
# -- --- --- --- --- --- --- ---
#<powershell-launch-subprocess>
tclsh $scriptname $args
#tclsh $scriptname $args
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
# -- --- --- --- --- --- --- ---

346
src/modules/punk/mix/templates/utility/scriptappwrappers/multishell1.cmd

@ -1,41 +1,65 @@
: "[rename set s;proc Hide x {proc $x args {}};Hide :]" "\$(function : {<#pwsh#>})" ^
set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershell;proc Hide x {proc $x args {}}; 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 @ECHO; Hide ); Hide (;Hide echo; Hide @REM]#not necessary but can help avoid errs in testing"
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" +
: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH'
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT - except for first double quoted section.
: shebang line is not required on unix or windows and will reduce functionality and/or portability.
: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care.
@GOTO :skip_perl_pod_start ^;
=begin excludeperl
: skip_perl_pod_start
: Continuation char at end of this line and rem with curly-braces used to exlude Tcl from the whole cmd block \
: {
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT. 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.
@REM ############################################################################################################################
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, sh and/or powershelll (powershell.exe or pwsh.exe)
@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 On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: pmix scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM e.g from within a running punkshell: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used)
@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@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
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh'"
@SET "shells[10]=pwsh"
@SET "shells[11]=sh"
@set "shells[12]=bash"
@SET "shells[13]=tclsh"
@SET "validshelltypes= powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________"
@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 32 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
: <nextshell>
@SET "nextshell=13"
@SET "nextshellpath[win32___________]=tclsh___________________________"
@SET "nextshelltype[win32___________]=tcl_____________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________"
@SET "nextshelltype[dragonflybsd____]=tcl_____________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[freebsd_________]=tcl_____________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[netbsd__________]=tcl_____________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[linux___________]=tcl_____________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[macosx__________]=tcl_____________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[other___________]=tcl_____________"
: </nextshell>
@rem asadmin is for automatic elevation to administrator. Separate window will be created (seems unavoidable with current elevation mechanism) and user will still get security prompt (probably reasonable).
: <asadmin>
@SET "asadmin=0"
: </asadmin>
@REM nextshell set to index for validshells .eg 10 for pwsh
@REM @ECHO nextshell is %nextshell%
@SET "selected=!shells[%nextshell%]!"
@REM @ECHO selected %selected%
@CALL SET "keyRemoved=%%validshells:'!selected!'=%%"
@REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM @SET "selected_shelltype=%nextshelltype[win32___________]%"
@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 ## ### ### ### ### ### ### ### ### ### ### ### ### ###
@ -49,16 +73,16 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@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 -- pmix scriptwrap.checkfile <filepath>
@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 pmix scriptwrap.checkfile is still recommended.
@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 pmix scriptwrap.checkfile doesn't check all such boundaries.
@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
@ -89,22 +113,36 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
)
@SET "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs"
@SET arglist=%*
@IF "%1"=="PUNK-ELEVATED" (
@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
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@GOTO skip_privileges
:getPrivileges
@IF '%1'=='PUNK-ELEVATED' (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@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.cmd", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script in new windows due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B
@ -113,7 +151,7 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@REM setlocal & pushd .
@PUSHD .
@cd /d %~dp0
@IF "%1"=="PUNK-ELEVATED" (
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
@DEL "%vbsGetPrivileges%" 1>nul 2>nul
@SET arglist=%arglist:~14%
)
@ -124,7 +162,7 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@if not exist "%~dp0%~n0.ps1" (
@SET need_ps1=1
) ELSE (
fc "%~dp0%~n0.cmd" "%~dp0%~n0.ps1" >nul || goto different
fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different
@REM @ECHO "files same"
@SET need_ps1=0
)
@ -134,10 +172,10 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@SET need_ps1=1
:pscontinue
@IF !need_ps1!==1 (
COPY "%~dp0%~n0.cmd" "%~dp0%~n0.ps1" >NUL
COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL
)
@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "!shells[%nextshell%]!"=="pwsh" (
@IF "%selected_shelltype_trimmed%"=="powershell" (
REM pws vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time
REM test availability of preferred option of powershell7+ pwsh
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL
@ -145,7 +183,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% & SET task_exitcode=!errorlevel!
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
REM CALL powershell -nop -nol -c write-host powershell-found
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %*
@ -153,24 +192,31 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "!shells[%nextshell%]!"=="bash" (
IF "%selected_shelltype_trimmed%"=="wslbash" (
CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%"
!shells[%nextshell%]! "!wslpath!%fname%" %arglist% & SET task_exitcode=!errorlevel!
%selected_shellpath_trimmed% "!wslpath!%fname%" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
REM probably tclsh or sh
IF NOT "x%keyRemoved%"=="x%validshells%" (
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
!shells[%nextshell%]! "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel!
REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode
%selected_shellpath_trimmed% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
) ELSE (
ECHO %fname% has invalid nextshell value ^(%nextshell%^) !shells[%nextshell%]! valid options are %validshells%
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
SET task_exitcode=66
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
GOTO :exit_multishell
)
)
)
@REM batch file library functions
@REM boundary padding
@GOTO :endlib
:getWslPath
@ -179,7 +225,9 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@SET "name=%~nx1"
@SET "drive=%~d1"
@SET "rtrn=%~2"
@SET "result=/mnt/%drive:~0,1%%_path:\=/%%name%"
@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%"
@ -227,6 +275,7 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
)
@EXIT /B
@REM boundary padding
@REM boundary padding
:getNormalizedScriptTail
@SETLOCAL
@SET "result=%~nx0"
@ -245,6 +294,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash
@ -289,7 +340,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
)
)
@EXIT /B
@REM boundary padding
@REM boundary padding
:stringToUpper
@SETLOCAL
@SET "rtrn=%~2"
@ -307,7 +359,47 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
)
)
@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
:stringTrimTrailingUnderscores
@SETLOCAL
@SET "rtrn=%~2"
@SET "string=%~1"
@SET "trimstring=%~1"
@REM trim up to 31 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 "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"
@ -328,6 +420,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
:endlib
: \
@REM padding
@REM padding
@REM @SET taskexit_code=!errorlevel! & goto :exit_multishell
@GOTO :exit_multishell
# }
@ -348,9 +442,9 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
rename set ""; rename s set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@
namespace eval ::punk::multishell {
set last_script_root [file dirname [file normalize ${argv0}/__]]
set last_script_root [file dirname [file normalize ${::argv0}/__]]
set last_script [file dirname [file normalize [info script]/__]]
if {[info exists argv0] &&
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
@ -365,7 +459,7 @@ namespace eval ::punk::multishell {
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}/__]]"
puts stderr "Info: script_root: [file dirname [file normalize ${::argv0}/__]]"
return 0
}
return [set ::punk::multishell::is_main($script_name)]
@ -380,9 +474,15 @@ namespace eval ::punk::multishell {
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-payload>
#</tcl-payload>
#<tcl-pre-launch-subprocess>
#</tcl-pre-launch-subprocess>
#<tcl-launch-subprocess>
#</tcl-launch-subprocess>
#<tcl-post-launch-subprocess>
#</tcl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- ---
@ -414,33 +514,33 @@ if false==false # else {
# -- 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 @call line above ... to something like @call sh ... @call bash .. or @call env sh ... etc as appropriate
# -- adjust the %nextshell% value above
# -- if sh/bash scripting needs to run on windows too.
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
exitcode=0
#printf "start of bash or sh code"
#<shell-payload-pre-tcl>
#</shell-payload-pre-tcl>
#<shell-pre-launch-subprocess>
#</shell-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<shell-launch-tcl>
exitcode=0 ;#default assumption
#<shell-launch-subprocess>
#-- sh/bash launches Tcl here instead of shebang line at top
#-- use exec to use exitcode (if any) directly from the tcl script
#exec /usr/bin/env tclsh "$0" "$@"
#-- alternative - can run sh/bash script after the tcl call.
/usr/bin/env tclsh "$0" "$@"
exitcode=$?
#echo "tcl exitcode: ${exitcode}"
#echo "sh/bash reporting tcl exitcode: ${exitcode}"
#-- override exitcode example
#exit 66
#</shell-launch-tcl>
#</shell-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<shell-payload-post-tcl>
#</shell-payload-post-tcl>
#<shell-post-launch-subprocess>
#</shell-post-launch-subprocess>
#printf "sh/bash done \n"
@ -448,7 +548,57 @@ exitcode=$?
#------------------------------------------------------
fi
exit ${exitcode}
# end hide sh/bash block from Tcl
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- Perl script section
# -- leave the script below as is, if all that is required is launching the Tcl payload"
# --
# -- Note that perl script isn't called by default when simply running this script by name
# -- adjust the nextshell value at the top of the script to point to perl
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
=cut
#!/user/bin/perl
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
my $exit_code = 0;
#use ExtUtils::Installed;
#my $installed = ExtUtils::Installed->new();
#my @modules = $installed->modules();
#print "Modules:\n";
#foreach my $m (@modules) {
# print "$m\n";
#}
# -- --- ---
my $scriptname = $0;
print "perl $scriptname\n";
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
}
#<perl-pre-launch-subprocess>
#</perl-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-launch-subprocess>
$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-post-launch-subprocess>
#</perl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload
exit $exit_code;
__END__
# end hide sh/bash/perl block from Tcl
# This comment with closing brace should stay in place whether if commented or not }
#------------------------------------------------------
# begin hide powershell-block from Tcl - only needed if Tcl didn't exit or return above
@ -460,9 +610,76 @@ if 0 {
# -- 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
$scriptname = GetScriptName
function GetDynamicParamDictionary {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true, Mandatory=$true)]
[string] $CommandName
)
begin {
# Get a list of params that should be ignored (they're common to all advanced functions)
$CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) |
Get-Member -MemberType Properties |
Select-Object -ExpandProperty Name
}
process {
# Create the dictionary that this scriptblock will return:
$DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# Convert to object array and get rid of Common params:
(Get-Command $CommandName | select -exp Parameters).GetEnumerator() |
Where-Object { $CommonParameterNames -notcontains $_.Key } |
ForEach-Object {
$DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter (
$_.Key,
$_.Value.ParameterType,
$_.Value.Attributes
)
$DynParamDictionary.Add($_.Key, $DynamicParameter)
}
# Return the dynamic parameters
return $DynParamDictionary
}
}
# GetDynamicParamDictionary
# - This can make it easier to share a single set of param definitions between functions
# - sample usage
#function ParameterDefinitions {
# param(
# [Parameter(Mandatory)][string] $myargument
# )
#}
#function psmain {
# [CmdletBinding()]
# param()
# dynamicparam { GetDynamicParamDictionary ParameterDefinitions }
# process {
# #called once with $PSBoundParameters dictionary
# #can be used to validate arguments, or set a simpler variable name for access
# switch ($PSBoundParameters.keys) {
# 'myargumentname' {
# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
# }
# #...
# }
# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
# #...
# }
# }
# end {
# #Main function logic
# Write-Host "myargumentname value is: $myargumentname"
# #myotherfunction @PSBoundParameters
# }
#}
#psmain @args
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
@ -470,22 +687,22 @@ $scriptname = getScriptName
#"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
#<powershell-payload-pre-tcl>
#</powershell-payload-pre-tcl>
#<powershell-pre-launch-subprocess>
#</powershell-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<powershell-launch-tcl>
#<powershell-launch-subprocess>
tclsh $scriptname $args
#</powershell-launch-tcl>
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<powershell-payload-post-tcl>
#</powershell-payload-post-tcl>
#<powershell-post-launch-subprocess>
#</powershell-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
Exit $LASTEXITCODE
# heredoc2 for powershell to ignore block below
$1 = @'
@ -498,7 +715,7 @@ $1 = @'
: \
@REM @ECHO exitcode: !task_exitcode!
: \
@IF "%1"=="PUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
: \
@EXIT /B !task_exitcode!
# cmd has exited
@ -509,6 +726,7 @@ $1 = @'
# -- powershell multiline comment
#>
<#
no script engine should try to run me
# id:tailblock1
# <ctrl-z>


240
src/modules/punk/mix/templates/utility/scriptappwrappers/multishell2.cmd

@ -1,34 +1,29 @@
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" +
: "[rename set s;proc Hide x {proc $x args {}};Hide :]" "\$(function : {<#pwsh#>})" ^
set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershell;proc Hide x {proc $x args {}}; 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 @ECHO; Hide ); Hide (;Hide echo; 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 \
: {
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT. 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.
@REM ############################################################################################################################
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, sh and/or powershelll (powershell.exe or pwsh.exe)
@REM It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################
@REM On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM e.g from within a running punkshell: pmix scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used)
@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh' (^14^) 'perl'"
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh'"
@SET "shells[10]=pwsh"
@SET "shells[11]=sh"
@set "shells[12]=bash"
@SET "shells[13]=tclsh"
@SET "shells[14]=perl"
: <nextshell>
@SET "nextshell=13"
: </nextshell>
@ -54,16 +49,16 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@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 -- pmix 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 -- 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 pmix 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 -- It is unknown what versions of cmd interpreters behave this way - and pmix 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
@ -94,40 +89,22 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$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" (
@IF "%1"=="PUNK-ELEVATED" (
GOTO :gotPrivileges
)
@IF !asadmin!==1 (
net file 1>NUL 2>NUL
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges )
)
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@GOTO skip_privileges
:getPrivileges
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@IF '%1'=='PUNK-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 UAC.ShellExecute "%~dp0%~n0.cmd", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script in new windows due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B
@ -136,7 +113,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@REM setlocal & pushd .
@PUSHD .
@cd /d %~dp0
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
@IF "%1"=="PUNK-ELEVATED" (
@DEL "%vbsGetPrivileges%" 1>nul 2>nul
@SET arglist=%arglist:~14%
)
@ -147,7 +124,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@if not exist "%~dp0%~n0.ps1" (
@SET need_ps1=1
) ELSE (
fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different
fc "%~dp0%~n0.cmd" "%~dp0%~n0.ps1" >nul || goto different
@REM @ECHO "files same"
@SET need_ps1=0
)
@ -157,7 +134,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@SET need_ps1=1
:pscontinue
@IF !need_ps1!==1 (
COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL
COPY "%~dp0%~n0.cmd" "%~dp0%~n0.ps1" >NUL
)
@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "!shells[%nextshell%]!"=="pwsh" (
@ -168,8 +145,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
SET task_exitcode=!errorlevel!
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% & SET task_exitcode=!errorlevel!
) ELSE (
REM CALL powershell -nop -nol -c write-host powershell-found
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %*
@ -180,26 +156,21 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
IF "!shells[%nextshell%]!"=="bash" (
CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%"
!shells[%nextshell%]! "!wslpath!%fname%" %arglist%
SET task_exitcode=!errorlevel!
!shells[%nextshell%]! "!wslpath!%fname%" %arglist% & SET task_exitcode=!errorlevel!
) ELSE (
REM probably tclsh or sh
IF NOT "x%keyRemoved%"=="x%validshells%" (
REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
!shells[%nextshell%]! "%~dp0%fname%" %arglist%
SET task_exitcode=!errorlevel!
!shells[%nextshell%]! "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel!
) ELSE (
ECHO %fname% has invalid nextshell value ^(%nextshell%^) !shells[%nextshell%]! valid options are %validshells%
SET task_exitcode=66
@REM boundary padding
@REM boundary padding
GOTO :exit_multishell
)
)
)
@REM batch file library functions
@REM boundary padding
@GOTO :endlib
:getWslPath
@ -256,7 +227,6 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
)
@EXIT /B
@REM boundary padding
@REM boundary padding
:getNormalizedScriptTail
@SETLOCAL
@SET "result=%~nx0"
@ -275,8 +245,6 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash
@ -412,15 +380,9 @@ namespace eval ::punk::multishell {
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-pre-launch-subprocess>
#</tcl-pre-launch-subprocess>
#<tcl-launch-subprocess>
#</tcl-launch-subproces>
#<tcl-payload>
#</tcl-payload>
#<tcl-post-launch-subprocess>
#</tcl-post-launch-subproces>
# -- --- --- --- --- --- --- --- --- --- --- ---
@ -452,33 +414,33 @@ if false==false # else {
# -- 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
# -- adjust @call line above ... to something like @call sh ... @call bash .. or @call env sh ... etc as appropriate
# -- if sh/bash scripting needs to run on windows too.
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
exitcode=0
#printf "start of bash or sh code"
#<shell-pre-launch-subprocess>
#</shell-pre-launch-subprocess>
#<shell-payload-pre-tcl>
#</shell-payload-pre-tcl>
# -- --- --- --- --- --- --- ---
#<shell-launch-subprocess>
#<shell-launch-tcl>
exitcode=0 ;#default assumption
#-- 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}"
#echo "tcl exitcode: ${exitcode}"
#-- override exitcode example
#exit 66
#</shell-launch-subprocess>
#</shell-launch-tcl>
# -- --- --- --- --- --- --- ---
#<shell-post-launch-subprocess>
#</shell-post-launch-subproces>
#<shell-payload-post-tcl>
#</shell-payload-post-tcl>
#printf "sh/bash done \n"
@ -486,57 +448,7 @@ exitcode=$?
#------------------------------------------------------
fi
exit ${exitcode}
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- Perl script section
# -- leave the script below as is, if all that is required is launching the Tcl payload"
# --
# -- Note that perl script isn't called by default when simply running this script by name
# -- adjust the nextshell value at the top of the script to point to perl
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
=cut
#!/user/bin/perl
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
my $exit_code = 0;
#use ExtUtils::Installed;
#my $installed = ExtUtils::Installed->new();
#my @modules = $installed->modules();
#print "Modules:\n";
#foreach my $m (@modules) {
# print "$m\n";
#}
# -- --- ---
my $scriptname = $0;
print "perl $scriptname\n";
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
}
#<perl-pre-launch-subprocess>
#</perl-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-launch-subprocess>
$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-post-launch-subprocess>
#</perl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload
exit $exit_code;
__END__
# end hide sh/bash/perl block from Tcl
# end hide sh/bash 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
@ -548,76 +460,9 @@ if 0 {
# -- Do not edit if current file is the .ps1
# -- Edit the corresponding .cmd and it will autocopy
# -- unbalanced braces { } here *even in comments* will cause problems if there was no Tcl exit or return above
# -- custom script should generally go below the begin_powershell_payload line
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
function GetScriptName { $myInvocation.ScriptName }
$scriptname = GetScriptName
function GetDynamicParamDictionary {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true, Mandatory=$true)]
[string] $CommandName
)
begin {
# Get a list of params that should be ignored (they're common to all advanced functions)
$CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) |
Get-Member -MemberType Properties |
Select-Object -ExpandProperty Name
}
process {
# Create the dictionary that this scriptblock will return:
$DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# Convert to object array and get rid of Common params:
(Get-Command $CommandName | select -exp Parameters).GetEnumerator() |
Where-Object { $CommonParameterNames -notcontains $_.Key } |
ForEach-Object {
$DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter (
$_.Key,
$_.Value.ParameterType,
$_.Value.Attributes
)
$DynParamDictionary.Add($_.Key, $DynamicParameter)
}
# Return the dynamic parameters
return $DynParamDictionary
}
}
# GetDynamicParamDictionary
# - This can make it easier to share a single set of param definitions between functions
# - sample usage
#function ParameterDefinitions {
# param(
# [Parameter(Mandatory)][string] $myargument
# )
#}
#function psmain {
# [CmdletBinding()]
# param()
# dynamicparam { GetDynamicParamDictionary ParameterDefinitions }
# process {
# #called once with $PSBoundParameters dictionary
# #can be used to validate arguments, or set a simpler variable name for access
# switch ($PSBoundParameters.keys) {
# 'myargumentname' {
# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
# }
# #...
# }
# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
# #...
# }
# }
# end {
# #Main function logic
# Write-Host "myargumentname value is: $myargumentname"
# #myotherfunction @PSBoundParameters
# }
#}
#psmain @args
$scriptname = getScriptName
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
@ -625,22 +470,22 @@ function GetDynamicParamDictionary {
#"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
#<powershell-pre-launch-subprocess>
#</powershell-pre-launch-subprocess>
#<powershell-payload-pre-tcl>
#</powershell-payload-pre-tcl>
# -- --- --- --- --- --- --- ---
#<powershell-launch-subprocess>
#<powershell-launch-tcl>
tclsh $scriptname $args
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
#</powershell-launch-tcl>
# -- --- --- --- --- --- --- ---
#<powershell-post-launch-subprocess>
#</powershell-post-launch-subprocess>
#<powershell-payload-post-tcl>
#</powershell-payload-post-tcl>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
Exit $LASTEXITCODE
# heredoc2 for powershell to ignore block below
$1 = @'
@ -653,7 +498,7 @@ $1 = @'
: \
@REM @ECHO exitcode: !task_exitcode!
: \
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
@IF "%1"=="PUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
: \
@EXIT /B !task_exitcode!
# cmd has exited
@ -664,7 +509,6 @@ $1 = @'
# -- powershell multiline comment
#>
<#
no script engine should try to run me
# id:tailblock1
# <ctrl-z>


680
src/modules/punk/mix/templates/utility/scriptappwrappers/multishell3.cmd

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

# </ctrl-z>
# -- unreachable by tcl directly if ctrl-z character is in the <ctrl-z> section above. (but file can be read and split on \x1A)
# -- Potential for zip and/or base64 contents, but we can't stop pwsh parser from slurping in the data
# -- so for example a plain text tar archive could cause problems depending on the content.
# -- final line in file must be the powershell multiline comment terminator or other data it can handle.
# -- e.g plain # comment lines will work too
# -- (for example a powershell digital signature is a # commented block of data at the end of the file)
#>

1
src/modules/punk/pdf-999999.0a1.0.tm

@ -1894,6 +1894,7 @@ tcl::namespace::eval punk::pdf::lib {
dict set map %marker% "[punk::ansi::a bold cyan]MERGEDBLOCK[punk::ansi::a]"
puts $outc [string map $map $opt_blocksep]
}
set blockresult ""
if {$opt_shrink_textfree_blocks} {
set teststripped [punk::ansi::ansistrip $mergedblock]
if {[string trim $teststripped] ne ""} {

29
src/project_layouts/custom/_project/punk.basic/src/make.tcl

@ -2839,6 +2839,10 @@ foreach vfstail $vfs_tails {
#zipfs mkimg replaces the entire zipped vfs in the runtime - so we need the original data to be part of our targetvfs.
puts stdout "building $vfsname.new with zipfs vfsdir:$vfstail cwd: [pwd]"
file mkdir $targetvfs
set raw_runtime $buildfolder/raw_$runtime_fullname
if {[info commands ::tcl::zipfs::mount] ne ""} {
set rtmountpoint //zipfs:/rtmounts/$runtime_fullname
if {![file exists $rtmountpoint]} {
@ -2859,7 +2863,6 @@ foreach vfstail $vfs_tails {
#which unfortunately Tcl does by default after the 2021 'fix' :(
#https://core.tcl-lang.org/tcl/tktview/aaa84fbbc5
set raw_runtime $buildfolder/raw_$runtime_fullname
if {[file exists $rtmountpoint]} {
merge_over $rtmountpoint $targetvfs
#see if we can extract the exe part
@ -2885,11 +2888,29 @@ foreach vfstail $vfs_tails {
punk::zip::extract_preamble $building_runtime $raw_runtime
}
} else {
#the input building_runtime wasn't mountable - so presumably a plain executable
#set building_runtime $buildfolder/build_$runtime_fullname ;#working copy of runtime executable - (possibly with kit/zipfs/cookfs etc attached!)
#set raw_runtime $buildfolder/raw_$runtime_fullname
#the input building_runtime wasn't mountable as a zip - so presumably a plain executable
#runtime executable possibly with kit/cookfs etc attached?
#If not - init.tcl probably won't be found? should we even proceed ??
puts stderr "\x1b\[31mWARNING the runtime was not mountable as a zip - which means tcl_library was not extracted - the executable may not work with zipfs attached!\x1b\[m"
file copy -force $building_runtime $raw_runtime
}
} else {
package require punk::zip
#tcl we are calling with doesn't have zipfs - can't mount
puts stderr "WARNING: tcl shell '[info nameofexecutable]' being used to build doesn't have zipfs - falling back to punk::zip::extract_preamble"
set extractedzipfile $buildfolder/extracted_$runtime_fullname.zip
set extractedzipfolder $buildfolder/extracted_$runtime_fullname
file delete $raw_runtime
file delete $extractedzipfile
file delete -force $extractedzipfolder
punk::zip::extract_preamble $building_runtime $raw_runtime $extractedzipfile
package require zipfile::decode
zipfile::decode::open $extractedzipfile
set archiveinfo [zipfile::decode::archive]
zipfile::decode::unzip $archiveinfo $extractedzipfolder
#todo - verify that init.tcl etc are present?
merge_over $extractedzipfolder $targetvfs
}
merge_over $sourcefolder/vfs/_vfscommon.vfs $targetvfs

261
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/scriptwrap-0.1.0.tm

@ -53,6 +53,7 @@ package require punk::args
package require punk::mix
package require punk::mix::base
package require punk::fileline
package require punk::ansi
#*** !doctools
@ -78,6 +79,7 @@ namespace eval punk::mix::commandset::scriptwrap {
#[list_begin definitions]
namespace export {[a-z]*}
namespace import ::punk::ansi::a ::punk::ansi::a+
namespace eval fileline {
namespace import ::punk::fileline::lib::*
@ -807,7 +809,7 @@ namespace eval punk::mix::commandset::scriptwrap {
}
return $result
}
#specific filepath to just wrap one script at the xxx-pre-launch-suprocess site
#specific filepath to just wrap one script at the xxx-payload site
#scriptset name to substitute multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
#set usage ""
#append usage "Use directly with the script file to wrap, or supply the name of a scriptset" \n
@ -830,6 +832,17 @@ namespace eval punk::mix::commandset::scriptwrap {
if {[tomlish::dict::path::exists $tomldict {.application.template}]} {
dict set resultd template [tomlish::dict::path::get $tomldict {.application.template.value}]
}
if {[tomlish::dict::path::exists $tomldict {.application.as_admin}]} {
set val [tomlish::dict::path::get $tomldict {.application.as_admin.value}]
if {$val && 1} {
dict set resultd as_admin 1
} else {
dict set resultd as_admin 0
}
} else {
dict set resultd as_admin 0
}
set scripts [list]
if {[tomlish::dict::path::exists $tomldict {.application.scripts.value}]} {
set arrvalues [tomlish::dict::path::get $tomldict {.application.scripts.value}]
@ -872,6 +885,60 @@ namespace eval punk::mix::commandset::scriptwrap {
return $resultd
}
proc _default_configd {} {
set configd [dict create]
dict set configd template ""
dict set configd as_admin 0
dict set configd scripts [list]
dict set configd default_outputfile ""
dict set configd default_nextshellpath ""
dict set configd default_nextshelltype "none"
foreach os {win32 dragonflybsd freebsd netbsd linux macosx other} {
dict set configd $os outputfile ""
dict set configd $os nextshellpath ""
dict set configd $os nextshelltype "none"
}
return $configd
}
proc _get_nextshell_script {configd} {
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________"
#delimeters
#: <<nextshell_start>>
#: <<nextshell_end>>
set script ""
dict for {k v} $configd {
if {[llength $v] % 2 == 0 && [dict exists $v nextshelltype]} {
set os $k
set n [expr {16 - [string length $os]}]
set _os [string repeat _ $n]
set path [dict get $v nextshellpath]
set n [expr {64 - [string length $path]}]
set _path [string repeat _ $n]
set type [dict get $v nextshelltype]
set n [expr {16 - [string length $type]}]
set _type [string repeat _ $n]
append script "@SET \"nextshellpath\[$os$_os\]=$path$_path\"" \n
append script "@SET \"nextshelltype\[$os$_os\]=$type$_type\"" \n
}
}
set script [string trimright $script \n]
return $script
}
punk::args::define {
@id -id ::punk::mix::commandset::scriptwrap::multishell
@cmd -name punk::mix::commandset::scriptwrap::multishell\
@ -914,22 +981,22 @@ namespace eval punk::mix::commandset::scriptwrap {
-returnextra -type boolean -default 0
@values -minvalues 0 -maxvalues 0
}
#: <nextshell>
#@SET "nextshellpath[win32___________]=tclsh___________________________"
#: <<nextshell_start>>
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________"
#: </nextshell>
#: <<nextshell_end>>
proc multishell {args} {
set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell]
lassign [dict values $argd] leaders opts values received
@ -947,7 +1014,7 @@ namespace eval punk::mix::commandset::scriptwrap {
set startdir [pwd]
set allowed_extensions [list tcl ps1 sh bash pl]
#TODO - distinct sections for sh vs bash? needs experiments..
#for now we use shell-pre-launch-subprocess etc
#for now we use shell-payload etc
#set extension_langs [list tcl tcl ps1 powershell sh sh bash bash pl perl]
set extension_langs [list tcl tcl ps1 powershell sh shell bash shell pl perl]
@ -990,13 +1057,15 @@ namespace eval punk::mix::commandset::scriptwrap {
}
set list_input_files [list]
set has_config 0
set configd [dict create]
if {$scriptset ne ""} {
puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'"
#.toml file may or may not exist
if {[file exists ${scriptset}_wrap.toml]} {
puts stdout "Loading configuration from $scriptdir/${scriptset}_wrap.toml"
puts stdout "[a bold green]Loading configuration from $scriptdir/${scriptset}_wrap.toml[a]"
set configd [_read_scriptset_wrap_tomlfile $scriptdir/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts {
@ -1008,6 +1077,7 @@ namespace eval punk::mix::commandset::scriptwrap {
return false
}
} else {
set configd [_default_configd]
puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml"
puts stdout "Will look for the following scripts in $scriptdir"
foreach e $allowed_extensions {
@ -1018,12 +1088,17 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptdir/$scriptset.$e
}
}
dict set configd scripts $list_input_files
}
} else {
set configd [_default_configd]
#expect a single script
if {[file exists $specified_path]} {
lappend list_input_files $specified_path
}
dict set configd scripts [lmap f $list_input_files {file tail $f}]
set ftail [file tail $specified_path]
dict set configd default_outputfile [file rootname $ftail]
}
set found_script [expr {[llength $list_input_files] > 0}]
@ -1061,8 +1136,9 @@ namespace eval punk::mix::commandset::scriptwrap {
if {$scriptset ne ""} {
#.toml file may or may not exist
if {[file exists $scriptroot/${scriptset}_wrap.toml]} {
puts stdout "Loading configuration from $scriptroot/${scriptset}_wrap.toml"
puts stdout "[a green]Loading configuration from $scriptroot/${scriptset}_wrap.toml[a]"
set configd [_read_scriptset_wrap_tomlfile $scriptroot/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts {
@ -1084,6 +1160,8 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptroot/$scriptset.$e
}
}
set configd [_default_configd]
dict set configd scripts [lmap f $list_input_files {file tail $f}]
}
} else {
#expect a single script
@ -1094,6 +1172,8 @@ namespace eval punk::mix::commandset::scriptwrap {
}
lappend list_input_files $scriptroot/$filepath_or_scriptset
}
set configd [_default_configd]
dict set configd scripts [lmap f $list_input_files {file tail $f}]
}
set found_script [expr {[llength $list_input_files] > 0}]
@ -1111,7 +1191,7 @@ namespace eval punk::mix::commandset::scriptwrap {
}
#assertion - customwrapper_folder var exists - but might be empty
if {[dict exists $configd template]} {
if {[dict get $configd template] ne ""} {
set templatename [dict get $configd template]
} else {
if {$opt_template eq "\uFFFF"} {
@ -1119,6 +1199,7 @@ namespace eval punk::mix::commandset::scriptwrap {
} else {
set templatename $opt_template
}
dict set configd template $templatename
}
set templatename_root [file rootname [file tail $templatename]]
@ -1199,7 +1280,6 @@ namespace eval punk::mix::commandset::scriptwrap {
#todo
#output_file extension may also depend on the template being used.. and/or the <scriptset>_wrap.toml config
if {[dict size $configd]} {
package require platform
set thisplatform [string tolower [platform::identify]]
set ptype [lindex [split $thisplatform -] 0]
@ -1209,11 +1289,17 @@ namespace eval punk::mix::commandset::scriptwrap {
set ptype other
}
}
if {$has_config} {
set out [dict get $configd $ptype outputfile]
if {[string trim $out] ne ""} {
set output_file [file join $output_folder $out]
} else {
#can be empty for this os if configured that way in xxx_wrap.toml
set output_file ""
}
} else {
#no _wrap.toml file available
if {$::tcl_platform(platform) eq "windows"} {
if {$ptype eq "win32"} {
set output_extension .cmd
} else {
set output_extension .sh
@ -1224,6 +1310,11 @@ namespace eval punk::mix::commandset::scriptwrap {
set infile [lindex $list_input_files 0]
set output_file [file join $output_folder [file rootname [file tail $infile]]$output_extension]
}
dict set configd $ptype outputfile [file tail $output_file]
}
if {$output_file eq ""} {
puts stderr "No output file configured for platform $ptype"
return
}
@ -1236,7 +1327,7 @@ namespace eval punk::mix::commandset::scriptwrap {
puts stdout "wrap_in_multishell: target file $output_file already exists. File size: [$objFile_existing chunklen] Line count: [$objFile_existing linecount]"
if {!$opt_force} {
if {$opt_askme} {
set answer [util::askuser "Do you want to overwrite $output_file? Y|N"]
set answer [util::askuser "Do you want to [a bold white Red]overwrite[a] [a bold red]$output_file[a]? Y|N"]
if {[string tolower $answer] ne "y"} {
puts stderr "aborting due to user response '$answer' (required Y or y to proceed) use -force 1 or -askme 0 to avoid prompts."
$objFile_existing destroy
@ -1264,36 +1355,33 @@ namespace eval punk::mix::commandset::scriptwrap {
puts stdout $ln
}
puts stdout "-----------------------------------------------\n"
#foreach ln $template_lines {
#}
if {[llength $list_input_files] > 1} {
#todo
puts stderr "Sorry - only single input file supported. Supply a file extension or use a <scriptset>_wrap.toml config with a single input file for now - implementation incomplete"
return false
}
#todo - split template at each <ext-*-subprocess> etc marker and build a dict of parts
#if {[llength $list_input_files] > 1} {
# #todo
# puts stderr "Sorry - only single input file supported. Supply a file extension or use a <scriptset>_wrap.toml config with a single input file for now - implementation incomplete"
# return false
#}
#hack - process one input
set filepath [lindex $list_input_files 0]
set lang_data [dict create]
foreach filepath $list_input_files {
set script_ext [string trim [file extension $filepath] .]
set lang [dict get $extension_langs [string tolower $script_ext]]
set fdscript [open $filepath r]
fconfigure $fdscript -translation binary
set script_data [read $fdscript]
close $fdscript
puts stdout "Read [string length $script_data] bytes of template data.."
puts stdout "Read [string length $script_data] bytes of template data for lang: $lang"
set script_lines [split $script_data \n]
puts stdout "Displaying first 3 lines of your script between dashed lines..."
dict set lang_data $lang $script_lines
puts stdout "Displaying first 3 lines of your script [file tail $filepath] between dashed lines..."
puts stdout "[a green]$filepath[a]"
puts stdout "-----------------------------------------------"
foreach ln [lrange $script_lines 0 3] {
puts stdout $ln
}
puts stdout "-----------------------------------------------\n"
puts stdout "Target for above script data is '$output_file'"
set script_ext [string trim [file extension $filepath] .]
set lang [dict get $extension_langs [string tolower $script_ext]]
puts stdout "Language of script being wrapped is $lang"
puts stdout "Target for script data is '$output_file'"
puts stdout "Language of script being wrapped is [a bold yellow]$lang[a]"
if {$opt_askme} {
set answer [util::askuser "Does this look correct? Y|N"]
if {[string tolower $answer] ne "y"} {
@ -1301,55 +1389,88 @@ namespace eval punk::mix::commandset::scriptwrap {
return
}
}
}
set start_idx 0
set end_idx 0
set line_idx 0
set existing_payload [list]
set template_ranges [list]
set data_items [list]
set trange [list 0]
set line_idx -1
set opentag ""
foreach ln $template_lines {
if {[string match "#<$lang-pre-launch-subprocess>*" $ln]} {
set start_idx $line_idx
} elseif {[string match "#</$lang-pre-launch-subprocess>*" $ln]} {
set end_idx $line_idx
break
} elseif {$start_idx > 0} {
if {$end_idx > 0} {
lappend existing_payload [string trim $ln]
incr line_idx
if {$opentag eq ""} {
if {[string match ": <<asadmin_start>>*" $ln]} {
set opentag asadmin
lset trange 1 $line_idx ;#include tag in template
lappend template_ranges $trange
set trange [list $line_idx]
set asadmin [dict get $configd as_admin]
set scr "@SET \"asadmin=$asadmin\""
lappend data_items $scr
} elseif {[string match ": <<nextshell_start>>*" $ln]} {
set opentag nextshell
lset trange 1 $line_idx
lappend template_ranges $trange
set trange [list $line_idx]
set nextshell_script [_get_nextshell_script $configd]
#puts stderr "-------------------"
#puts stderr "$nextshell_script"
lappend data_items $nextshell_script
} elseif {[string match "#<*-payload>*" $ln]} {
regexp {#<(.*)-payload>.*} $ln _ lang
if {[dict exists $lang_data $lang]} {
set script_lines [dict get $lang_data $lang]
set opentag payload-$lang
lset trange 1 $line_idx
lappend template_ranges $trange
lappend data_items [join $script_lines \n]
}
}
} else {
switch -- [string range $opentag 0 6] {
asadmin {
if {[string match ": <<asadmin_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
incr line_idx
}
if {($start_idx == 0) || ($end_idx == 0)} {
error "wrap_in_multishell was unable to find payload area in template marked with #<$lang-pre-launch-subprocess> and #</$lang-pre-launch-subprocess> on separate lines"
nextshe {
if {[string match ": <<nextshell_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
set existing_string [join $existing_payload \n]
if {[string length [string trim $existing_string]]} {
puts stdout "EXISTING <$lang-pre-launch-subprocess> PAYLOAD!!"
puts stdout "-----------------------------------------------\n"
puts stdout $existing_string
puts stdout "-----------------------------------------------\n"
error "wrap_in_multishell found existing payload for language $lang ... aborting."
#todo - allow overwrite only in files outside of punkshell distribution?
if 0 {
puts stderr "Found existing $lang payload.. overwrite?"
if {$opt_askme} {
set answer [util::askuser "Are you sure you want to replace the $lang payload shown above? Y|N"]
if {[string tolower $answer] ne "y"} {
puts stderr "mix new aborting due to user response '$answer' (required Y|y to proceed) use -askme 0 to avoid prompts."
return
}
payload {
set lang [string range $opentag 8 end] ;#payload-xxx
if {[string match "#</$lang-payload>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
}
}
}
if {$opentag eq ""} {
lset trange 1 end
lappend template_ranges $trange
} else {
error "multishell - unable to find closing tag for '$opentag'"
}
set newscript ""
foreach trange $template_ranges item $data_items {
append newscript [join [lrange $template_lines {*}$trange] \n]
if {$item ne ""} {
append newscript \n $item \n
} else {
append newscript \n
}
}
set tpl_head_lines [lrange $template_lines 0 $start_idx] ;#include tag line
set tpl_tail_lines [lrange $template_lines $end_idx end]
set newscript [join $tpl_head_lines \n]\n[join $script_lines \n]\n[join $tpl_tail_lines \n]
puts stdout "New script is [string length $newscript] bytes"
puts stdout $newscript
set fdtarget [open $output_file w]
fconfigure $fdtarget -translation binary
puts -nonewline $fdtarget $newscript

29
src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl

@ -2839,6 +2839,10 @@ foreach vfstail $vfs_tails {
#zipfs mkimg replaces the entire zipped vfs in the runtime - so we need the original data to be part of our targetvfs.
puts stdout "building $vfsname.new with zipfs vfsdir:$vfstail cwd: [pwd]"
file mkdir $targetvfs
set raw_runtime $buildfolder/raw_$runtime_fullname
if {[info commands ::tcl::zipfs::mount] ne ""} {
set rtmountpoint //zipfs:/rtmounts/$runtime_fullname
if {![file exists $rtmountpoint]} {
@ -2859,7 +2863,6 @@ foreach vfstail $vfs_tails {
#which unfortunately Tcl does by default after the 2021 'fix' :(
#https://core.tcl-lang.org/tcl/tktview/aaa84fbbc5
set raw_runtime $buildfolder/raw_$runtime_fullname
if {[file exists $rtmountpoint]} {
merge_over $rtmountpoint $targetvfs
#see if we can extract the exe part
@ -2885,11 +2888,29 @@ foreach vfstail $vfs_tails {
punk::zip::extract_preamble $building_runtime $raw_runtime
}
} else {
#the input building_runtime wasn't mountable - so presumably a plain executable
#set building_runtime $buildfolder/build_$runtime_fullname ;#working copy of runtime executable - (possibly with kit/zipfs/cookfs etc attached!)
#set raw_runtime $buildfolder/raw_$runtime_fullname
#the input building_runtime wasn't mountable as a zip - so presumably a plain executable
#runtime executable possibly with kit/cookfs etc attached?
#If not - init.tcl probably won't be found? should we even proceed ??
puts stderr "\x1b\[31mWARNING the runtime was not mountable as a zip - which means tcl_library was not extracted - the executable may not work with zipfs attached!\x1b\[m"
file copy -force $building_runtime $raw_runtime
}
} else {
package require punk::zip
#tcl we are calling with doesn't have zipfs - can't mount
puts stderr "WARNING: tcl shell '[info nameofexecutable]' being used to build doesn't have zipfs - falling back to punk::zip::extract_preamble"
set extractedzipfile $buildfolder/extracted_$runtime_fullname.zip
set extractedzipfolder $buildfolder/extracted_$runtime_fullname
file delete $raw_runtime
file delete $extractedzipfile
file delete -force $extractedzipfolder
punk::zip::extract_preamble $building_runtime $raw_runtime $extractedzipfile
package require zipfile::decode
zipfile::decode::open $extractedzipfile
set archiveinfo [zipfile::decode::archive]
zipfile::decode::unzip $archiveinfo $extractedzipfolder
#todo - verify that init.tcl etc are present?
merge_over $extractedzipfolder $targetvfs
}
merge_over $sourcefolder/vfs/_vfscommon.vfs $targetvfs

261
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/scriptwrap-0.1.0.tm

@ -53,6 +53,7 @@ package require punk::args
package require punk::mix
package require punk::mix::base
package require punk::fileline
package require punk::ansi
#*** !doctools
@ -78,6 +79,7 @@ namespace eval punk::mix::commandset::scriptwrap {
#[list_begin definitions]
namespace export {[a-z]*}
namespace import ::punk::ansi::a ::punk::ansi::a+
namespace eval fileline {
namespace import ::punk::fileline::lib::*
@ -807,7 +809,7 @@ namespace eval punk::mix::commandset::scriptwrap {
}
return $result
}
#specific filepath to just wrap one script at the xxx-pre-launch-suprocess site
#specific filepath to just wrap one script at the xxx-payload site
#scriptset name to substitute multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
#set usage ""
#append usage "Use directly with the script file to wrap, or supply the name of a scriptset" \n
@ -830,6 +832,17 @@ namespace eval punk::mix::commandset::scriptwrap {
if {[tomlish::dict::path::exists $tomldict {.application.template}]} {
dict set resultd template [tomlish::dict::path::get $tomldict {.application.template.value}]
}
if {[tomlish::dict::path::exists $tomldict {.application.as_admin}]} {
set val [tomlish::dict::path::get $tomldict {.application.as_admin.value}]
if {$val && 1} {
dict set resultd as_admin 1
} else {
dict set resultd as_admin 0
}
} else {
dict set resultd as_admin 0
}
set scripts [list]
if {[tomlish::dict::path::exists $tomldict {.application.scripts.value}]} {
set arrvalues [tomlish::dict::path::get $tomldict {.application.scripts.value}]
@ -872,6 +885,60 @@ namespace eval punk::mix::commandset::scriptwrap {
return $resultd
}
proc _default_configd {} {
set configd [dict create]
dict set configd template ""
dict set configd as_admin 0
dict set configd scripts [list]
dict set configd default_outputfile ""
dict set configd default_nextshellpath ""
dict set configd default_nextshelltype "none"
foreach os {win32 dragonflybsd freebsd netbsd linux macosx other} {
dict set configd $os outputfile ""
dict set configd $os nextshellpath ""
dict set configd $os nextshelltype "none"
}
return $configd
}
proc _get_nextshell_script {configd} {
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________"
#delimeters
#: <<nextshell_start>>
#: <<nextshell_end>>
set script ""
dict for {k v} $configd {
if {[llength $v] % 2 == 0 && [dict exists $v nextshelltype]} {
set os $k
set n [expr {16 - [string length $os]}]
set _os [string repeat _ $n]
set path [dict get $v nextshellpath]
set n [expr {64 - [string length $path]}]
set _path [string repeat _ $n]
set type [dict get $v nextshelltype]
set n [expr {16 - [string length $type]}]
set _type [string repeat _ $n]
append script "@SET \"nextshellpath\[$os$_os\]=$path$_path\"" \n
append script "@SET \"nextshelltype\[$os$_os\]=$type$_type\"" \n
}
}
set script [string trimright $script \n]
return $script
}
punk::args::define {
@id -id ::punk::mix::commandset::scriptwrap::multishell
@cmd -name punk::mix::commandset::scriptwrap::multishell\
@ -914,22 +981,22 @@ namespace eval punk::mix::commandset::scriptwrap {
-returnextra -type boolean -default 0
@values -minvalues 0 -maxvalues 0
}
#: <nextshell>
#@SET "nextshellpath[win32___________]=tclsh___________________________"
#: <<nextshell_start>>
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________"
#: </nextshell>
#: <<nextshell_end>>
proc multishell {args} {
set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell]
lassign [dict values $argd] leaders opts values received
@ -947,7 +1014,7 @@ namespace eval punk::mix::commandset::scriptwrap {
set startdir [pwd]
set allowed_extensions [list tcl ps1 sh bash pl]
#TODO - distinct sections for sh vs bash? needs experiments..
#for now we use shell-pre-launch-subprocess etc
#for now we use shell-payload etc
#set extension_langs [list tcl tcl ps1 powershell sh sh bash bash pl perl]
set extension_langs [list tcl tcl ps1 powershell sh shell bash shell pl perl]
@ -990,13 +1057,15 @@ namespace eval punk::mix::commandset::scriptwrap {
}
set list_input_files [list]
set has_config 0
set configd [dict create]
if {$scriptset ne ""} {
puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'"
#.toml file may or may not exist
if {[file exists ${scriptset}_wrap.toml]} {
puts stdout "Loading configuration from $scriptdir/${scriptset}_wrap.toml"
puts stdout "[a bold green]Loading configuration from $scriptdir/${scriptset}_wrap.toml[a]"
set configd [_read_scriptset_wrap_tomlfile $scriptdir/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts {
@ -1008,6 +1077,7 @@ namespace eval punk::mix::commandset::scriptwrap {
return false
}
} else {
set configd [_default_configd]
puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml"
puts stdout "Will look for the following scripts in $scriptdir"
foreach e $allowed_extensions {
@ -1018,12 +1088,17 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptdir/$scriptset.$e
}
}
dict set configd scripts $list_input_files
}
} else {
set configd [_default_configd]
#expect a single script
if {[file exists $specified_path]} {
lappend list_input_files $specified_path
}
dict set configd scripts [lmap f $list_input_files {file tail $f}]
set ftail [file tail $specified_path]
dict set configd default_outputfile [file rootname $ftail]
}
set found_script [expr {[llength $list_input_files] > 0}]
@ -1061,8 +1136,9 @@ namespace eval punk::mix::commandset::scriptwrap {
if {$scriptset ne ""} {
#.toml file may or may not exist
if {[file exists $scriptroot/${scriptset}_wrap.toml]} {
puts stdout "Loading configuration from $scriptroot/${scriptset}_wrap.toml"
puts stdout "[a green]Loading configuration from $scriptroot/${scriptset}_wrap.toml[a]"
set configd [_read_scriptset_wrap_tomlfile $scriptroot/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts {
@ -1084,6 +1160,8 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptroot/$scriptset.$e
}
}
set configd [_default_configd]
dict set configd scripts [lmap f $list_input_files {file tail $f}]
}
} else {
#expect a single script
@ -1094,6 +1172,8 @@ namespace eval punk::mix::commandset::scriptwrap {
}
lappend list_input_files $scriptroot/$filepath_or_scriptset
}
set configd [_default_configd]
dict set configd scripts [lmap f $list_input_files {file tail $f}]
}
set found_script [expr {[llength $list_input_files] > 0}]
@ -1111,7 +1191,7 @@ namespace eval punk::mix::commandset::scriptwrap {
}
#assertion - customwrapper_folder var exists - but might be empty
if {[dict exists $configd template]} {
if {[dict get $configd template] ne ""} {
set templatename [dict get $configd template]
} else {
if {$opt_template eq "\uFFFF"} {
@ -1119,6 +1199,7 @@ namespace eval punk::mix::commandset::scriptwrap {
} else {
set templatename $opt_template
}
dict set configd template $templatename
}
set templatename_root [file rootname [file tail $templatename]]
@ -1199,7 +1280,6 @@ namespace eval punk::mix::commandset::scriptwrap {
#todo
#output_file extension may also depend on the template being used.. and/or the <scriptset>_wrap.toml config
if {[dict size $configd]} {
package require platform
set thisplatform [string tolower [platform::identify]]
set ptype [lindex [split $thisplatform -] 0]
@ -1209,11 +1289,17 @@ namespace eval punk::mix::commandset::scriptwrap {
set ptype other
}
}
if {$has_config} {
set out [dict get $configd $ptype outputfile]
if {[string trim $out] ne ""} {
set output_file [file join $output_folder $out]
} else {
#can be empty for this os if configured that way in xxx_wrap.toml
set output_file ""
}
} else {
#no _wrap.toml file available
if {$::tcl_platform(platform) eq "windows"} {
if {$ptype eq "win32"} {
set output_extension .cmd
} else {
set output_extension .sh
@ -1224,6 +1310,11 @@ namespace eval punk::mix::commandset::scriptwrap {
set infile [lindex $list_input_files 0]
set output_file [file join $output_folder [file rootname [file tail $infile]]$output_extension]
}
dict set configd $ptype outputfile [file tail $output_file]
}
if {$output_file eq ""} {
puts stderr "No output file configured for platform $ptype"
return
}
@ -1236,7 +1327,7 @@ namespace eval punk::mix::commandset::scriptwrap {
puts stdout "wrap_in_multishell: target file $output_file already exists. File size: [$objFile_existing chunklen] Line count: [$objFile_existing linecount]"
if {!$opt_force} {
if {$opt_askme} {
set answer [util::askuser "Do you want to overwrite $output_file? Y|N"]
set answer [util::askuser "Do you want to [a bold white Red]overwrite[a] [a bold red]$output_file[a]? Y|N"]
if {[string tolower $answer] ne "y"} {
puts stderr "aborting due to user response '$answer' (required Y or y to proceed) use -force 1 or -askme 0 to avoid prompts."
$objFile_existing destroy
@ -1264,36 +1355,33 @@ namespace eval punk::mix::commandset::scriptwrap {
puts stdout $ln
}
puts stdout "-----------------------------------------------\n"
#foreach ln $template_lines {
#}
if {[llength $list_input_files] > 1} {
#todo
puts stderr "Sorry - only single input file supported. Supply a file extension or use a <scriptset>_wrap.toml config with a single input file for now - implementation incomplete"
return false
}
#todo - split template at each <ext-*-subprocess> etc marker and build a dict of parts
#if {[llength $list_input_files] > 1} {
# #todo
# puts stderr "Sorry - only single input file supported. Supply a file extension or use a <scriptset>_wrap.toml config with a single input file for now - implementation incomplete"
# return false
#}
#hack - process one input
set filepath [lindex $list_input_files 0]
set lang_data [dict create]
foreach filepath $list_input_files {
set script_ext [string trim [file extension $filepath] .]
set lang [dict get $extension_langs [string tolower $script_ext]]
set fdscript [open $filepath r]
fconfigure $fdscript -translation binary
set script_data [read $fdscript]
close $fdscript
puts stdout "Read [string length $script_data] bytes of template data.."
puts stdout "Read [string length $script_data] bytes of template data for lang: $lang"
set script_lines [split $script_data \n]
puts stdout "Displaying first 3 lines of your script between dashed lines..."
dict set lang_data $lang $script_lines
puts stdout "Displaying first 3 lines of your script [file tail $filepath] between dashed lines..."
puts stdout "[a green]$filepath[a]"
puts stdout "-----------------------------------------------"
foreach ln [lrange $script_lines 0 3] {
puts stdout $ln
}
puts stdout "-----------------------------------------------\n"
puts stdout "Target for above script data is '$output_file'"
set script_ext [string trim [file extension $filepath] .]
set lang [dict get $extension_langs [string tolower $script_ext]]
puts stdout "Language of script being wrapped is $lang"
puts stdout "Target for script data is '$output_file'"
puts stdout "Language of script being wrapped is [a bold yellow]$lang[a]"
if {$opt_askme} {
set answer [util::askuser "Does this look correct? Y|N"]
if {[string tolower $answer] ne "y"} {
@ -1301,55 +1389,88 @@ namespace eval punk::mix::commandset::scriptwrap {
return
}
}
}
set start_idx 0
set end_idx 0
set line_idx 0
set existing_payload [list]
set template_ranges [list]
set data_items [list]
set trange [list 0]
set line_idx -1
set opentag ""
foreach ln $template_lines {
if {[string match "#<$lang-pre-launch-subprocess>*" $ln]} {
set start_idx $line_idx
} elseif {[string match "#</$lang-pre-launch-subprocess>*" $ln]} {
set end_idx $line_idx
break
} elseif {$start_idx > 0} {
if {$end_idx > 0} {
lappend existing_payload [string trim $ln]
incr line_idx
if {$opentag eq ""} {
if {[string match ": <<asadmin_start>>*" $ln]} {
set opentag asadmin
lset trange 1 $line_idx ;#include tag in template
lappend template_ranges $trange
set trange [list $line_idx]
set asadmin [dict get $configd as_admin]
set scr "@SET \"asadmin=$asadmin\""
lappend data_items $scr
} elseif {[string match ": <<nextshell_start>>*" $ln]} {
set opentag nextshell
lset trange 1 $line_idx
lappend template_ranges $trange
set trange [list $line_idx]
set nextshell_script [_get_nextshell_script $configd]
#puts stderr "-------------------"
#puts stderr "$nextshell_script"
lappend data_items $nextshell_script
} elseif {[string match "#<*-payload>*" $ln]} {
regexp {#<(.*)-payload>.*} $ln _ lang
if {[dict exists $lang_data $lang]} {
set script_lines [dict get $lang_data $lang]
set opentag payload-$lang
lset trange 1 $line_idx
lappend template_ranges $trange
lappend data_items [join $script_lines \n]
}
}
} else {
switch -- [string range $opentag 0 6] {
asadmin {
if {[string match ": <<asadmin_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
incr line_idx
}
if {($start_idx == 0) || ($end_idx == 0)} {
error "wrap_in_multishell was unable to find payload area in template marked with #<$lang-pre-launch-subprocess> and #</$lang-pre-launch-subprocess> on separate lines"
nextshe {
if {[string match ": <<nextshell_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
set existing_string [join $existing_payload \n]
if {[string length [string trim $existing_string]]} {
puts stdout "EXISTING <$lang-pre-launch-subprocess> PAYLOAD!!"
puts stdout "-----------------------------------------------\n"
puts stdout $existing_string
puts stdout "-----------------------------------------------\n"
error "wrap_in_multishell found existing payload for language $lang ... aborting."
#todo - allow overwrite only in files outside of punkshell distribution?
if 0 {
puts stderr "Found existing $lang payload.. overwrite?"
if {$opt_askme} {
set answer [util::askuser "Are you sure you want to replace the $lang payload shown above? Y|N"]
if {[string tolower $answer] ne "y"} {
puts stderr "mix new aborting due to user response '$answer' (required Y|y to proceed) use -askme 0 to avoid prompts."
return
}
payload {
set lang [string range $opentag 8 end] ;#payload-xxx
if {[string match "#</$lang-payload>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
}
}
}
if {$opentag eq ""} {
lset trange 1 end
lappend template_ranges $trange
} else {
error "multishell - unable to find closing tag for '$opentag'"
}
set newscript ""
foreach trange $template_ranges item $data_items {
append newscript [join [lrange $template_lines {*}$trange] \n]
if {$item ne ""} {
append newscript \n $item \n
} else {
append newscript \n
}
}
set tpl_head_lines [lrange $template_lines 0 $start_idx] ;#include tag line
set tpl_tail_lines [lrange $template_lines $end_idx end]
set newscript [join $tpl_head_lines \n]\n[join $script_lines \n]\n[join $tpl_tail_lines \n]
puts stdout "New script is [string length $newscript] bytes"
puts stdout $newscript
set fdtarget [open $output_file w]
fconfigure $fdtarget -translation binary
puts -nonewline $fdtarget $newscript

29
src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl

@ -2839,6 +2839,10 @@ foreach vfstail $vfs_tails {
#zipfs mkimg replaces the entire zipped vfs in the runtime - so we need the original data to be part of our targetvfs.
puts stdout "building $vfsname.new with zipfs vfsdir:$vfstail cwd: [pwd]"
file mkdir $targetvfs
set raw_runtime $buildfolder/raw_$runtime_fullname
if {[info commands ::tcl::zipfs::mount] ne ""} {
set rtmountpoint //zipfs:/rtmounts/$runtime_fullname
if {![file exists $rtmountpoint]} {
@ -2859,7 +2863,6 @@ foreach vfstail $vfs_tails {
#which unfortunately Tcl does by default after the 2021 'fix' :(
#https://core.tcl-lang.org/tcl/tktview/aaa84fbbc5
set raw_runtime $buildfolder/raw_$runtime_fullname
if {[file exists $rtmountpoint]} {
merge_over $rtmountpoint $targetvfs
#see if we can extract the exe part
@ -2885,11 +2888,29 @@ foreach vfstail $vfs_tails {
punk::zip::extract_preamble $building_runtime $raw_runtime
}
} else {
#the input building_runtime wasn't mountable - so presumably a plain executable
#set building_runtime $buildfolder/build_$runtime_fullname ;#working copy of runtime executable - (possibly with kit/zipfs/cookfs etc attached!)
#set raw_runtime $buildfolder/raw_$runtime_fullname
#the input building_runtime wasn't mountable as a zip - so presumably a plain executable
#runtime executable possibly with kit/cookfs etc attached?
#If not - init.tcl probably won't be found? should we even proceed ??
puts stderr "\x1b\[31mWARNING the runtime was not mountable as a zip - which means tcl_library was not extracted - the executable may not work with zipfs attached!\x1b\[m"
file copy -force $building_runtime $raw_runtime
}
} else {
package require punk::zip
#tcl we are calling with doesn't have zipfs - can't mount
puts stderr "WARNING: tcl shell '[info nameofexecutable]' being used to build doesn't have zipfs - falling back to punk::zip::extract_preamble"
set extractedzipfile $buildfolder/extracted_$runtime_fullname.zip
set extractedzipfolder $buildfolder/extracted_$runtime_fullname
file delete $raw_runtime
file delete $extractedzipfile
file delete -force $extractedzipfolder
punk::zip::extract_preamble $building_runtime $raw_runtime $extractedzipfile
package require zipfile::decode
zipfile::decode::open $extractedzipfile
set archiveinfo [zipfile::decode::archive]
zipfile::decode::unzip $archiveinfo $extractedzipfolder
#todo - verify that init.tcl etc are present?
merge_over $extractedzipfolder $targetvfs
}
merge_over $sourcefolder/vfs/_vfscommon.vfs $targetvfs

33
src/scriptapps/bits.pl

@ -0,0 +1,33 @@
eval {
use Win32::Service;
};
if (exists $INC{'Win32/Service.pm'}) {
print "Win32::Service is loaded\n";
} else {
print "Win32::Service is not loaded\n";
print "Attempting to install from cpan..\n";
use CPAN;
my $module = 'Win32::Service';
eval {
CPAN::Shell->install($module);
};
if ($@) {
print "Error installing $module: $@";
} else {
print "$module installed\n";
}
}
eval {
use Win32::Service;
};
if ($@) {
print "Unable to load or install Win32::Service. error: $@\n";
} else {
my $service = "BITS";
if (Win32::Service::StartService('', $service)) {
print "$service started\n";
} else {
print "Failed to start $service\n";
}
}

9
src/scriptapps/bits.ps1

@ -0,0 +1,9 @@
if ((Get-Service -name "BITS").Status -eq "Running") {
Write-Host "OK - BITS services is running"
} else {
Write-Host "WARNING - BITS service does not seem to be running"
Write-Host "Attempting to start.."
Restart-Service BITS
Write-Host "BITS service status is: " (Get-Service -name "BITS").Status
}

1
src/scriptapps/bits.sh

@ -0,0 +1 @@
echo "The bits script is only available for the windows platform where it is used to start the BITS service"

30
src/scriptapps/bits_wrap.toml

@ -0,0 +1,30 @@
[application]
template="punk.multishell.cmd"
as_admin=true
#scripts=[
# "example.sh",
# "example.tcl"
#]
scripts=[
"bits.ps1",
"bits.sh",
"bits.pl"
]
default_outputfile="bits.sh"
default_nextshellpath="/usr/bin/env bash"
default_nextshelltype="bash"
#valid nextshelltype entries are: tcl perl powershell bash.
#nextshellpath entries must be 64 characters or less.
#win32.nextshellpath="perl"
#win32.nextshelltype="perl"
#win32.nextshellpath="c:/program files/powershell/7/pwsh.exe"
win32.nextshellpath="pwsh"
win32.nextshelltype="powershell"
win32.outputfile="bits.cmd"

10
src/scriptapps/example_wrap.toml

@ -1,5 +1,6 @@
[application]
template="punk.multishell.cmd"
as_admin=false
#scripts=[
# "example.sh",
@ -15,11 +16,16 @@
#valid nextshelltype entries are: tcl perl powershell bash.
#nextshellpath entries must be 32 characters or less.
#nextshellpath entries must be 64 characters or less.
# win32.nextshellpath="c:/program files/git/usr/bin/bash.exe"
# win32.nextshelltype="bash"
# win32.nextshellpath="c:/program files/powershell/7/pwsh.exe"
# win32.nextshelltype="powershell"
win32.nextshellpath="tclsh"
win32.nextshelltype="tcl"
win32.outputfile="example_out.bat"
win32.outputfile="example_out.cmd"
dragonflybsd.nextshellpath="/usr/bin/env tclsh"
dragonflybsd.nextshelltype="tcl"

50
src/scriptapps/fetchruntime.bash

@ -0,0 +1,50 @@
runtime_available = 0
if [[ "$OSTYPE" == "linux"* ]]; then
os="linux"
elif [[ "$OSTYPE" == "darwin"* ]]; then
os="macosx"
elif [[ "$OSTYPE" == "freebsd"* ]]; then
os="freebsd"
elif [[ "$OSTYPE" == "dragonflybsd"* ]]; then
os="dragonflybsd"
elif [[ "$OSTYPE" == "netbsd"* ]]; then
os="netbsd"
elif [[ "$OSTYPE" == "win32" ]]; then
os="win32"
runtime_available = 1
url="https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master/win64/tclsh901t.exe"
#scriptdir?
output="../src/runtime/tclsh901t.exe"
elif [[ "$OSTYPE" == "msys" ]]; then
echo MSYS
os="win32"
runtime_available = 1
#use 'command -v' (shell builtin preferred over external which)
interp = `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
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}}"
url="https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master/win64/tclsh901t.exe"
#scriptdir?
output="../src/runtime/tclsh901t.exe"
else
#os="$OSTYPE"
os="other"
fi
if [[ "$runtime_available" -eq 1 ]]; then
#test win32
echo "Attempting to download $url"
curl -SL "$output" "$url"
if [[ $? -eq 0 ]]; then
echo "File downloaded to $output"
else
echo "Error: Failed to download to $output"
fi
else
echo "No runtime currently available for $os"
fi

25
src/scriptapps/fetchruntime.ps1

@ -1,11 +1,26 @@
$url = "https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master/win64/tclkit86bi.exe"
$output = "$(join-path $PSScriptRoot "..\runtime\tclkit86bi.exe")"
#padding
$url = "https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master/win64/tclsh901t.exe"
$output = "$(join-path $PSScriptRoot "..\src\runtime\tclsh901t.exe")"
if (-not(Test-Path -Path $output -PathType Leaf)) {
if ((Get-Service -name "BITS").Status -eq "Running") {
Write-Host "OK - BITS services is running"
} else {
#If not running we can get a misleading error such as 'MUI error'
Write-Host "WARNING - BITS service does not seem to be running"
$answer = Read-Host "Do you want to start the Background Intelligent Transfer Service (BITS)? (Type Yes to start service)"
if ($answer -match "yes") {
#restart requires admin - use script marked for admin
#Restart-Service BITS
#Start-Process -FilePath "cmd.exe" -ArgumentList "-NoExit -NoNewWindow -File bits.cmd"
Start-Process -FilePath "bits.cmd" -ArgumentList "-NoExit -NoNewWindow" -Wait
#cmd /c bits.cmd
} else {
write-host "Unable to download. Exiting script.."
exit 1
}
}
if (-not(Test-Path -Path $output -PathType Leaf)) {
try {
#Invoke-WebRequest $url -OutFile

42
src/scriptapps/fetchruntime.tcl

@ -0,0 +1,42 @@
package require http
package require tls
http::register https 443 [list ::tls::socket -autoservername true]
package require platform
set plat [platform::identify]
set os [lindex [split $plat -] 0]
set runtime_available 0
set scriptdir [file dirname [info script]]
switch -- $os {
"win32" {
set url "https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master/win64/tclsh901t.exe"
set output [file join $scriptdir "../src/runtime/tclsh901t.exe"]
set runtime_available 1
}
"linux" {
puts stderr "No runtime currently available for linux"
}
"macosx" {
puts stderr "No runtime currently available for linux"
}
"freebsd" {
puts stderr "No runtime currently available for freebsd"
}
default {
puts stderr "No runtime currently available for $os"
}
}
if {$runtime_available} {
if {[file exists $output]} {
puts stderr "Runtime already found at $output"
exit 1
}
puts stdout "Attempting to download $url"
set fd [open $output wb]
set tok [http::geturl $url -channel $fd -binary 1]
close $fd
if {[http::status $tok] eq "ok" && [http::ncode $tok] == 200} {
puts "Download complete."
}
http::cleanup $tok
}

22
src/scriptapps/fetchruntime_old.ps1

@ -0,0 +1,22 @@
$url = "https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master/win64/tclkit901t.exe"
$output = "$(join-path $PSScriptRoot "..\runtime\tclkit901t.exe")"
#padding
if (-not(Test-Path -Path $output -PathType Leaf)) {
try {
#Invoke-WebRequest $url -OutFile
Import-Module BitsTransfer
Start-BitsTransfer -Source $url -Destination $output
Write-Host "Runtime saved at $output"
}
catch {
throw $_.Exception.Message
}
}
else {
Write-Host "Runtime already found at $output"
}

21
src/scriptapps/fetchruntime_wrap.toml

@ -0,0 +1,21 @@
[application]
template="punk.multishell.cmd"
as_admin=false
scripts=[
"fetchruntime.ps1",
"fetchruntime.tcl",
"fetchruntime.bash"
]
default_outputfile="fetchruntime.cmd"
default_nextshellpath="/usr/bin/env bash"
default_nextshelltype="bash"
#valid nextshelltype entries are: tcl perl powershell bash.
#nextshellpath entries must be 64 characters or less.
win32.nextshellpath="pwsh"
win32.nextshelltype="powershell"
win32.outputfile="fetchruntime.cmd"

261
src/vfs/_vfscommon.vfs/modules/punk/mix/commandset/scriptwrap-0.1.0.tm

@ -53,6 +53,7 @@ package require punk::args
package require punk::mix
package require punk::mix::base
package require punk::fileline
package require punk::ansi
#*** !doctools
@ -78,6 +79,7 @@ namespace eval punk::mix::commandset::scriptwrap {
#[list_begin definitions]
namespace export {[a-z]*}
namespace import ::punk::ansi::a ::punk::ansi::a+
namespace eval fileline {
namespace import ::punk::fileline::lib::*
@ -807,7 +809,7 @@ namespace eval punk::mix::commandset::scriptwrap {
}
return $result
}
#specific filepath to just wrap one script at the xxx-pre-launch-suprocess site
#specific filepath to just wrap one script at the xxx-payload site
#scriptset name to substitute multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
#set usage ""
#append usage "Use directly with the script file to wrap, or supply the name of a scriptset" \n
@ -830,6 +832,17 @@ namespace eval punk::mix::commandset::scriptwrap {
if {[tomlish::dict::path::exists $tomldict {.application.template}]} {
dict set resultd template [tomlish::dict::path::get $tomldict {.application.template.value}]
}
if {[tomlish::dict::path::exists $tomldict {.application.as_admin}]} {
set val [tomlish::dict::path::get $tomldict {.application.as_admin.value}]
if {$val && 1} {
dict set resultd as_admin 1
} else {
dict set resultd as_admin 0
}
} else {
dict set resultd as_admin 0
}
set scripts [list]
if {[tomlish::dict::path::exists $tomldict {.application.scripts.value}]} {
set arrvalues [tomlish::dict::path::get $tomldict {.application.scripts.value}]
@ -872,6 +885,60 @@ namespace eval punk::mix::commandset::scriptwrap {
return $resultd
}
proc _default_configd {} {
set configd [dict create]
dict set configd template ""
dict set configd as_admin 0
dict set configd scripts [list]
dict set configd default_outputfile ""
dict set configd default_nextshellpath ""
dict set configd default_nextshelltype "none"
foreach os {win32 dragonflybsd freebsd netbsd linux macosx other} {
dict set configd $os outputfile ""
dict set configd $os nextshellpath ""
dict set configd $os nextshelltype "none"
}
return $configd
}
proc _get_nextshell_script {configd} {
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________"
#delimeters
#: <<nextshell_start>>
#: <<nextshell_end>>
set script ""
dict for {k v} $configd {
if {[llength $v] % 2 == 0 && [dict exists $v nextshelltype]} {
set os $k
set n [expr {16 - [string length $os]}]
set _os [string repeat _ $n]
set path [dict get $v nextshellpath]
set n [expr {64 - [string length $path]}]
set _path [string repeat _ $n]
set type [dict get $v nextshelltype]
set n [expr {16 - [string length $type]}]
set _type [string repeat _ $n]
append script "@SET \"nextshellpath\[$os$_os\]=$path$_path\"" \n
append script "@SET \"nextshelltype\[$os$_os\]=$type$_type\"" \n
}
}
set script [string trimright $script \n]
return $script
}
punk::args::define {
@id -id ::punk::mix::commandset::scriptwrap::multishell
@cmd -name punk::mix::commandset::scriptwrap::multishell\
@ -914,22 +981,22 @@ namespace eval punk::mix::commandset::scriptwrap {
-returnextra -type boolean -default 0
@values -minvalues 0 -maxvalues 0
}
#: <nextshell>
#@SET "nextshellpath[win32___________]=tclsh___________________________"
#: <<nextshell_start>>
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________"
#: </nextshell>
#: <<nextshell_end>>
proc multishell {args} {
set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell]
lassign [dict values $argd] leaders opts values received
@ -947,7 +1014,7 @@ namespace eval punk::mix::commandset::scriptwrap {
set startdir [pwd]
set allowed_extensions [list tcl ps1 sh bash pl]
#TODO - distinct sections for sh vs bash? needs experiments..
#for now we use shell-pre-launch-subprocess etc
#for now we use shell-payload etc
#set extension_langs [list tcl tcl ps1 powershell sh sh bash bash pl perl]
set extension_langs [list tcl tcl ps1 powershell sh shell bash shell pl perl]
@ -990,13 +1057,15 @@ namespace eval punk::mix::commandset::scriptwrap {
}
set list_input_files [list]
set has_config 0
set configd [dict create]
if {$scriptset ne ""} {
puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'"
#.toml file may or may not exist
if {[file exists ${scriptset}_wrap.toml]} {
puts stdout "Loading configuration from $scriptdir/${scriptset}_wrap.toml"
puts stdout "[a bold green]Loading configuration from $scriptdir/${scriptset}_wrap.toml[a]"
set configd [_read_scriptset_wrap_tomlfile $scriptdir/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts {
@ -1008,6 +1077,7 @@ namespace eval punk::mix::commandset::scriptwrap {
return false
}
} else {
set configd [_default_configd]
puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml"
puts stdout "Will look for the following scripts in $scriptdir"
foreach e $allowed_extensions {
@ -1018,12 +1088,17 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptdir/$scriptset.$e
}
}
dict set configd scripts $list_input_files
}
} else {
set configd [_default_configd]
#expect a single script
if {[file exists $specified_path]} {
lappend list_input_files $specified_path
}
dict set configd scripts [lmap f $list_input_files {file tail $f}]
set ftail [file tail $specified_path]
dict set configd default_outputfile [file rootname $ftail]
}
set found_script [expr {[llength $list_input_files] > 0}]
@ -1061,8 +1136,9 @@ namespace eval punk::mix::commandset::scriptwrap {
if {$scriptset ne ""} {
#.toml file may or may not exist
if {[file exists $scriptroot/${scriptset}_wrap.toml]} {
puts stdout "Loading configuration from $scriptroot/${scriptset}_wrap.toml"
puts stdout "[a green]Loading configuration from $scriptroot/${scriptset}_wrap.toml[a]"
set configd [_read_scriptset_wrap_tomlfile $scriptroot/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts {
@ -1084,6 +1160,8 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptroot/$scriptset.$e
}
}
set configd [_default_configd]
dict set configd scripts [lmap f $list_input_files {file tail $f}]
}
} else {
#expect a single script
@ -1094,6 +1172,8 @@ namespace eval punk::mix::commandset::scriptwrap {
}
lappend list_input_files $scriptroot/$filepath_or_scriptset
}
set configd [_default_configd]
dict set configd scripts [lmap f $list_input_files {file tail $f}]
}
set found_script [expr {[llength $list_input_files] > 0}]
@ -1111,7 +1191,7 @@ namespace eval punk::mix::commandset::scriptwrap {
}
#assertion - customwrapper_folder var exists - but might be empty
if {[dict exists $configd template]} {
if {[dict get $configd template] ne ""} {
set templatename [dict get $configd template]
} else {
if {$opt_template eq "\uFFFF"} {
@ -1119,6 +1199,7 @@ namespace eval punk::mix::commandset::scriptwrap {
} else {
set templatename $opt_template
}
dict set configd template $templatename
}
set templatename_root [file rootname [file tail $templatename]]
@ -1199,7 +1280,6 @@ namespace eval punk::mix::commandset::scriptwrap {
#todo
#output_file extension may also depend on the template being used.. and/or the <scriptset>_wrap.toml config
if {[dict size $configd]} {
package require platform
set thisplatform [string tolower [platform::identify]]
set ptype [lindex [split $thisplatform -] 0]
@ -1209,11 +1289,17 @@ namespace eval punk::mix::commandset::scriptwrap {
set ptype other
}
}
if {$has_config} {
set out [dict get $configd $ptype outputfile]
if {[string trim $out] ne ""} {
set output_file [file join $output_folder $out]
} else {
#can be empty for this os if configured that way in xxx_wrap.toml
set output_file ""
}
} else {
#no _wrap.toml file available
if {$::tcl_platform(platform) eq "windows"} {
if {$ptype eq "win32"} {
set output_extension .cmd
} else {
set output_extension .sh
@ -1224,6 +1310,11 @@ namespace eval punk::mix::commandset::scriptwrap {
set infile [lindex $list_input_files 0]
set output_file [file join $output_folder [file rootname [file tail $infile]]$output_extension]
}
dict set configd $ptype outputfile [file tail $output_file]
}
if {$output_file eq ""} {
puts stderr "No output file configured for platform $ptype"
return
}
@ -1236,7 +1327,7 @@ namespace eval punk::mix::commandset::scriptwrap {
puts stdout "wrap_in_multishell: target file $output_file already exists. File size: [$objFile_existing chunklen] Line count: [$objFile_existing linecount]"
if {!$opt_force} {
if {$opt_askme} {
set answer [util::askuser "Do you want to overwrite $output_file? Y|N"]
set answer [util::askuser "Do you want to [a bold white Red]overwrite[a] [a bold red]$output_file[a]? Y|N"]
if {[string tolower $answer] ne "y"} {
puts stderr "aborting due to user response '$answer' (required Y or y to proceed) use -force 1 or -askme 0 to avoid prompts."
$objFile_existing destroy
@ -1264,36 +1355,33 @@ namespace eval punk::mix::commandset::scriptwrap {
puts stdout $ln
}
puts stdout "-----------------------------------------------\n"
#foreach ln $template_lines {
#}
if {[llength $list_input_files] > 1} {
#todo
puts stderr "Sorry - only single input file supported. Supply a file extension or use a <scriptset>_wrap.toml config with a single input file for now - implementation incomplete"
return false
}
#todo - split template at each <ext-*-subprocess> etc marker and build a dict of parts
#if {[llength $list_input_files] > 1} {
# #todo
# puts stderr "Sorry - only single input file supported. Supply a file extension or use a <scriptset>_wrap.toml config with a single input file for now - implementation incomplete"
# return false
#}
#hack - process one input
set filepath [lindex $list_input_files 0]
set lang_data [dict create]
foreach filepath $list_input_files {
set script_ext [string trim [file extension $filepath] .]
set lang [dict get $extension_langs [string tolower $script_ext]]
set fdscript [open $filepath r]
fconfigure $fdscript -translation binary
set script_data [read $fdscript]
close $fdscript
puts stdout "Read [string length $script_data] bytes of template data.."
puts stdout "Read [string length $script_data] bytes of template data for lang: $lang"
set script_lines [split $script_data \n]
puts stdout "Displaying first 3 lines of your script between dashed lines..."
dict set lang_data $lang $script_lines
puts stdout "Displaying first 3 lines of your script [file tail $filepath] between dashed lines..."
puts stdout "[a green]$filepath[a]"
puts stdout "-----------------------------------------------"
foreach ln [lrange $script_lines 0 3] {
puts stdout $ln
}
puts stdout "-----------------------------------------------\n"
puts stdout "Target for above script data is '$output_file'"
set script_ext [string trim [file extension $filepath] .]
set lang [dict get $extension_langs [string tolower $script_ext]]
puts stdout "Language of script being wrapped is $lang"
puts stdout "Target for script data is '$output_file'"
puts stdout "Language of script being wrapped is [a bold yellow]$lang[a]"
if {$opt_askme} {
set answer [util::askuser "Does this look correct? Y|N"]
if {[string tolower $answer] ne "y"} {
@ -1301,55 +1389,88 @@ namespace eval punk::mix::commandset::scriptwrap {
return
}
}
}
set start_idx 0
set end_idx 0
set line_idx 0
set existing_payload [list]
set template_ranges [list]
set data_items [list]
set trange [list 0]
set line_idx -1
set opentag ""
foreach ln $template_lines {
if {[string match "#<$lang-pre-launch-subprocess>*" $ln]} {
set start_idx $line_idx
} elseif {[string match "#</$lang-pre-launch-subprocess>*" $ln]} {
set end_idx $line_idx
break
} elseif {$start_idx > 0} {
if {$end_idx > 0} {
lappend existing_payload [string trim $ln]
incr line_idx
if {$opentag eq ""} {
if {[string match ": <<asadmin_start>>*" $ln]} {
set opentag asadmin
lset trange 1 $line_idx ;#include tag in template
lappend template_ranges $trange
set trange [list $line_idx]
set asadmin [dict get $configd as_admin]
set scr "@SET \"asadmin=$asadmin\""
lappend data_items $scr
} elseif {[string match ": <<nextshell_start>>*" $ln]} {
set opentag nextshell
lset trange 1 $line_idx
lappend template_ranges $trange
set trange [list $line_idx]
set nextshell_script [_get_nextshell_script $configd]
#puts stderr "-------------------"
#puts stderr "$nextshell_script"
lappend data_items $nextshell_script
} elseif {[string match "#<*-payload>*" $ln]} {
regexp {#<(.*)-payload>.*} $ln _ lang
if {[dict exists $lang_data $lang]} {
set script_lines [dict get $lang_data $lang]
set opentag payload-$lang
lset trange 1 $line_idx
lappend template_ranges $trange
lappend data_items [join $script_lines \n]
}
}
} else {
switch -- [string range $opentag 0 6] {
asadmin {
if {[string match ": <<asadmin_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
incr line_idx
}
if {($start_idx == 0) || ($end_idx == 0)} {
error "wrap_in_multishell was unable to find payload area in template marked with #<$lang-pre-launch-subprocess> and #</$lang-pre-launch-subprocess> on separate lines"
nextshe {
if {[string match ": <<nextshell_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
set existing_string [join $existing_payload \n]
if {[string length [string trim $existing_string]]} {
puts stdout "EXISTING <$lang-pre-launch-subprocess> PAYLOAD!!"
puts stdout "-----------------------------------------------\n"
puts stdout $existing_string
puts stdout "-----------------------------------------------\n"
error "wrap_in_multishell found existing payload for language $lang ... aborting."
#todo - allow overwrite only in files outside of punkshell distribution?
if 0 {
puts stderr "Found existing $lang payload.. overwrite?"
if {$opt_askme} {
set answer [util::askuser "Are you sure you want to replace the $lang payload shown above? Y|N"]
if {[string tolower $answer] ne "y"} {
puts stderr "mix new aborting due to user response '$answer' (required Y|y to proceed) use -askme 0 to avoid prompts."
return
}
payload {
set lang [string range $opentag 8 end] ;#payload-xxx
if {[string match "#</$lang-payload>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
}
}
}
if {$opentag eq ""} {
lset trange 1 end
lappend template_ranges $trange
} else {
error "multishell - unable to find closing tag for '$opentag'"
}
set newscript ""
foreach trange $template_ranges item $data_items {
append newscript [join [lrange $template_lines {*}$trange] \n]
if {$item ne ""} {
append newscript \n $item \n
} else {
append newscript \n
}
}
set tpl_head_lines [lrange $template_lines 0 $start_idx] ;#include tag line
set tpl_tail_lines [lrange $template_lines $end_idx end]
set newscript [join $tpl_head_lines \n]\n[join $script_lines \n]\n[join $tpl_tail_lines \n]
puts stdout "New script is [string length $newscript] bytes"
puts stdout $newscript
set fdtarget [open $output_file w]
fconfigure $fdtarget -translation binary
puts -nonewline $fdtarget $newscript

208
src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd

@ -1,5 +1,5 @@
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" +
@ -16,41 +16,41 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@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 On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used)
@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM 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 <inputfilepath> -outputfolder <folderpath>
@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
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshelltypes= powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________"
@SET "validshelltypes= 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 32 chars needed for a target, it can still be done but overall script padding may need checking/adjusting
@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
: <nextshell>
@SET "nextshellpath[win32___________]=tclsh___________________________"
: <<nextshell_start>>
@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
@SET "nextshelltype[win32___________]=tcl_____________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[dragonflybsd____]=tcl_____________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[freebsd_________]=tcl_____________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[netbsd__________]=tcl_____________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[linux___________]=tcl_____________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[macosx__________]=tcl_____________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[other___________]=tcl_____________"
: </nextshell>
: <<nextshell_end>>
@rem asadmin is for automatic elevation to administrator. Separate window will be created (seems unavoidable with current elevation mechanism) and user will still get security prompt (probably reasonable).
: <asadmin>
: <<asadmin_start>>
@SET "asadmin=0"
: </asadmin>
: <<asadmin_end>>
@REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM @SET "selected_shelltype=%nextshelltype[win32___________]%"
@SET "selected_shelltype=%nextshelltype[win32___________]%"
@ -143,7 +143,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script in new windows due to administrator elevation
@ECHO Launching script in new window due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B
@ -175,8 +175,11 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$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%"=="powershell" (
REM pws vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time
@IF "!selected_shelltype_trimmed!"=="none" (
SET selected_shelltype_trimmed=powershell
)
@IF "!selected_shelltype_trimmed!"=="powershell" (
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; write-host "statusmessage: pwsh-found" >NUL
SET pwshtest_exitcode=!errorlevel!
@ -192,7 +195,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "%selected_shelltype_trimmed%"=="wslbash" (
IF "!selected_shelltype_trimmed!"=="wslbash" (
CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%"
%selected_shellpath_trimmed% "!wslpath!%fname%" %arglist%
@ -203,6 +206,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
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% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
) ELSE (
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
@ -383,14 +387,15 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@SET "rtrn=%~2"
@SET "string=%~1"
@SET "trimstring=%~1"
@REM trim up to 31 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:###=%
@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 "" (
@ -439,7 +444,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
# -- e.g tclsh filename.cmd
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
rename set ""; rename s set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
rename set ""; rename S set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@
namespace eval ::punk::multishell {
set last_script_root [file dirname [file normalize ${::argv0}/__]]
@ -473,6 +478,9 @@ namespace eval ::punk::multishell {
#puts "argv0 : $::argv0"
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-payload>
puts stderr "No tcl code for this script. Try another program such as perl or bash"
#</tcl-payload>
#<tcl-pre-launch-subprocess>
#</tcl-pre-launch-subprocess>
@ -502,8 +510,20 @@ if {[::punk::multishell::is_main]} {
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload
# end hide from unix shells \
HEREDOC1B_HIDE_FROM_BASH_AND_SH
# csh/tcsh/sh/bash use oldschool backticks and sed lowest common denominator \
echo "script: `echo $0 | sed 's/^-//'`"
# csh/tcsh/sh/bash use oldschool backticks and sed lowest common denominator \
echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
#csh/tcsh diversion \
test "$argv[*]" != "[*]" && ( /usr/bin/env bash $argv[*]; exit )
#other non-bash diversion \
test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && /usr/bin/env bash $0
#review \
test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && exit
# sh/bash \
shift && set -- "${@:1:$#-1}"
#echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'`
#------------------------------------------------------
# -- This if block only needed if Tcl didn't exit or return above.
if false==false # else {
@ -518,10 +538,80 @@ if false==false # else {
# -- if sh/bash scripting needs to run on windows too.
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
if [[ "$OSTYPE" == "linux"* ]]; then
os="linux"
elif [[ "$OSTYPE" == "darwin"* ]]; then
os="macosx"
elif [[ "$OSTYPE" == "freebsd"* ]]; then
os="freebsd"
elif [[ "$OSTYPE" == "dragonflybsd"* ]]; then
os="dragonflybsd"
elif [[ "$OSTYPE" == "netbsd"* ]]; then
os="netbsd"
elif [[ "$OSTYPE" == "win32" ]]; then
os="win32"
elif [[ "$OSTYPE" == "msys" ]]; then
echo 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}}"
else
#os="$OSTYPE"
os="other"
fi
echo ostype: $OSTYPE
shellconfigline=$( sed -n "/: <<nextshell_start>>/{:a;n;/: <<nextshell_end>>/q;p;ba}" "$0" | grep $os)
#echo $shellconfigline;
if [[ $shellconfigline == *"nextshelltype"* ]]; then
echo "found config for os $os"
split1="${shellconfigline#*=}" #remove everything through the first '='
#echo "split1: $split1"
pathraw="${split1%%\"*}" #take everything before the quote - use %% to get longest match
pathraw="${pathraw//\"/}" #remove quote
nextshellpath="${pathraw/%_*/}" #remove trailing underscores (% = must match at end)
#echo "nextshellpath: $nextshellpath"
split2="${split1#*=}"
#echo "split2: $split2"
split2="${split2//\"/}"
nextshelltype="${split2/%_*/}"
echo "nextshelltype: $nextshelltype"
else
echo "unable to find config for os $os"
echo "shellconfigline: $shellconfigline"
nextshellpath=""
nextshelltype=""
fi
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 $nextshellpath on "$0"
#/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"
#<shell-payload>
echo "No bash code for this script. Try another program such as perl or tcl" >&2
#</shell-payload>
#<shell-pre-launch-subprocess>
#</shell-pre-launch-subprocess>
@ -531,8 +621,8 @@ exitcode=0
#-- 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=$?
#/usr/bin/env tclsh "$0" "$@"
#exitcode=$?
#echo "sh/bash reporting tcl exitcode: ${exitcode}"
#-- override exitcode example
#exit 66
@ -558,8 +648,18 @@ exit ${exitcode}
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
=cut
#!/user/bin/perl
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
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();
@ -571,13 +671,15 @@ my $exit_code = 0;
my $scriptname = $0;
print "perl $scriptname\n";
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
}
#<perl-payload>
print STDERR "No perl code for this script. Try another program such as tcl or bash";
#</perl-payload>
#<perl-pre-launch-subprocess>
#</perl-pre-launch-subprocess>
@ -585,7 +687,7 @@ foreach my $a(@ARGV) {
# -- --- --- --- --- --- --- ---
#<perl-launch-subprocess>
$exit_code=system("tclsh", $scriptname, @ARGV);
#$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess>
# -- --- --- --- --- --- --- ---
@ -680,12 +782,34 @@ function GetDynamicParamDictionary {
# }
#}
#psmain @args
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host
#"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
$startTag = ": <<asadmin_start>>"
$endTag = ": <<asadmin_end>>"
$fileContent = Get-Content $scriptname -Raw
$pattern = "(?s)$startTag(.*?)$endTag"
$matches = [regex]::Matches($fileContent,$pattern)
$admininfo = $matches[0].Groups[1].Value
$asadmin = 0
if ($matches.count) {
$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
Start-Process -FilePath "pwsh.exe" -ArgumentList "-NoProfile -NoExit -ExecutionPolicy Bypass -File $($MyInvocation.MyCommand.Path)" -Wait -Verb RunAs
Exit # Exit the current non-elevated process
}
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#<powershell-payload>
Write-Error "No powershell code for this script. Try another program such as perl, tcl or bash"
#</powershell-payload>
#<powershell-pre-launch-subprocess>
#</powershell-pre-launch-subprocess>
@ -693,7 +817,7 @@ function GetDynamicParamDictionary {
# -- --- --- --- --- --- --- ---
#<powershell-launch-subprocess>
tclsh $scriptname $args
#tclsh $scriptname $args
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
# -- --- --- --- --- --- --- ---

346
src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell1.cmd

@ -1,41 +1,65 @@
: "[rename set s;proc Hide x {proc $x args {}};Hide :]" "\$(function : {<#pwsh#>})" ^
set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershell;proc Hide x {proc $x args {}}; 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 @ECHO; Hide ); Hide (;Hide echo; Hide @REM]#not necessary but can help avoid errs in testing"
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" +
: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH'
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT - except for first double quoted section.
: shebang line is not required on unix or windows and will reduce functionality and/or portability.
: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care.
@GOTO :skip_perl_pod_start ^;
=begin excludeperl
: skip_perl_pod_start
: Continuation char at end of this line and rem with curly-braces used to exlude Tcl from the whole cmd block \
: {
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT. 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.
@REM ############################################################################################################################
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, sh and/or powershelll (powershell.exe or pwsh.exe)
@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 On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: pmix scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM e.g from within a running punkshell: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used)
@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@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
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh'"
@SET "shells[10]=pwsh"
@SET "shells[11]=sh"
@set "shells[12]=bash"
@SET "shells[13]=tclsh"
@SET "validshelltypes= powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________"
@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 32 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
: <nextshell>
@SET "nextshell=13"
@SET "nextshellpath[win32___________]=tclsh___________________________"
@SET "nextshelltype[win32___________]=tcl_____________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________"
@SET "nextshelltype[dragonflybsd____]=tcl_____________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[freebsd_________]=tcl_____________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[netbsd__________]=tcl_____________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[linux___________]=tcl_____________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[macosx__________]=tcl_____________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[other___________]=tcl_____________"
: </nextshell>
@rem asadmin is for automatic elevation to administrator. Separate window will be created (seems unavoidable with current elevation mechanism) and user will still get security prompt (probably reasonable).
: <asadmin>
@SET "asadmin=0"
: </asadmin>
@REM nextshell set to index for validshells .eg 10 for pwsh
@REM @ECHO nextshell is %nextshell%
@SET "selected=!shells[%nextshell%]!"
@REM @ECHO selected %selected%
@CALL SET "keyRemoved=%%validshells:'!selected!'=%%"
@REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM @SET "selected_shelltype=%nextshelltype[win32___________]%"
@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 ## ### ### ### ### ### ### ### ### ### ### ### ### ###
@ -49,16 +73,16 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@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 -- pmix scriptwrap.checkfile <filepath>
@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 pmix scriptwrap.checkfile is still recommended.
@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 pmix scriptwrap.checkfile doesn't check all such boundaries.
@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
@ -89,22 +113,36 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
)
@SET "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs"
@SET arglist=%*
@IF "%1"=="PUNK-ELEVATED" (
@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
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@GOTO skip_privileges
:getPrivileges
@IF '%1'=='PUNK-ELEVATED' (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@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.cmd", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script in new windows due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B
@ -113,7 +151,7 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@REM setlocal & pushd .
@PUSHD .
@cd /d %~dp0
@IF "%1"=="PUNK-ELEVATED" (
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
@DEL "%vbsGetPrivileges%" 1>nul 2>nul
@SET arglist=%arglist:~14%
)
@ -124,7 +162,7 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@if not exist "%~dp0%~n0.ps1" (
@SET need_ps1=1
) ELSE (
fc "%~dp0%~n0.cmd" "%~dp0%~n0.ps1" >nul || goto different
fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different
@REM @ECHO "files same"
@SET need_ps1=0
)
@ -134,10 +172,10 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@SET need_ps1=1
:pscontinue
@IF !need_ps1!==1 (
COPY "%~dp0%~n0.cmd" "%~dp0%~n0.ps1" >NUL
COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL
)
@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "!shells[%nextshell%]!"=="pwsh" (
@IF "%selected_shelltype_trimmed%"=="powershell" (
REM pws vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time
REM test availability of preferred option of powershell7+ pwsh
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL
@ -145,7 +183,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% & SET task_exitcode=!errorlevel!
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
REM CALL powershell -nop -nol -c write-host powershell-found
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %*
@ -153,24 +192,31 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "!shells[%nextshell%]!"=="bash" (
IF "%selected_shelltype_trimmed%"=="wslbash" (
CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%"
!shells[%nextshell%]! "!wslpath!%fname%" %arglist% & SET task_exitcode=!errorlevel!
%selected_shellpath_trimmed% "!wslpath!%fname%" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
REM probably tclsh or sh
IF NOT "x%keyRemoved%"=="x%validshells%" (
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
!shells[%nextshell%]! "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel!
REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode
%selected_shellpath_trimmed% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
) ELSE (
ECHO %fname% has invalid nextshell value ^(%nextshell%^) !shells[%nextshell%]! valid options are %validshells%
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
SET task_exitcode=66
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
GOTO :exit_multishell
)
)
)
@REM batch file library functions
@REM boundary padding
@GOTO :endlib
:getWslPath
@ -179,7 +225,9 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@SET "name=%~nx1"
@SET "drive=%~d1"
@SET "rtrn=%~2"
@SET "result=/mnt/%drive:~0,1%%_path:\=/%%name%"
@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%"
@ -227,6 +275,7 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
)
@EXIT /B
@REM boundary padding
@REM boundary padding
:getNormalizedScriptTail
@SETLOCAL
@SET "result=%~nx0"
@ -245,6 +294,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash
@ -289,7 +340,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
)
)
@EXIT /B
@REM boundary padding
@REM boundary padding
:stringToUpper
@SETLOCAL
@SET "rtrn=%~2"
@ -307,7 +359,47 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
)
)
@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
:stringTrimTrailingUnderscores
@SETLOCAL
@SET "rtrn=%~2"
@SET "string=%~1"
@SET "trimstring=%~1"
@REM trim up to 31 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 "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"
@ -328,6 +420,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
:endlib
: \
@REM padding
@REM padding
@REM @SET taskexit_code=!errorlevel! & goto :exit_multishell
@GOTO :exit_multishell
# }
@ -348,9 +442,9 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
rename set ""; rename s set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@
namespace eval ::punk::multishell {
set last_script_root [file dirname [file normalize ${argv0}/__]]
set last_script_root [file dirname [file normalize ${::argv0}/__]]
set last_script [file dirname [file normalize [info script]/__]]
if {[info exists argv0] &&
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
@ -365,7 +459,7 @@ namespace eval ::punk::multishell {
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}/__]]"
puts stderr "Info: script_root: [file dirname [file normalize ${::argv0}/__]]"
return 0
}
return [set ::punk::multishell::is_main($script_name)]
@ -380,9 +474,15 @@ namespace eval ::punk::multishell {
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-payload>
#</tcl-payload>
#<tcl-pre-launch-subprocess>
#</tcl-pre-launch-subprocess>
#<tcl-launch-subprocess>
#</tcl-launch-subprocess>
#<tcl-post-launch-subprocess>
#</tcl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- ---
@ -414,33 +514,33 @@ if false==false # else {
# -- 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 @call line above ... to something like @call sh ... @call bash .. or @call env sh ... etc as appropriate
# -- adjust the %nextshell% value above
# -- if sh/bash scripting needs to run on windows too.
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
exitcode=0
#printf "start of bash or sh code"
#<shell-payload-pre-tcl>
#</shell-payload-pre-tcl>
#<shell-pre-launch-subprocess>
#</shell-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<shell-launch-tcl>
exitcode=0 ;#default assumption
#<shell-launch-subprocess>
#-- sh/bash launches Tcl here instead of shebang line at top
#-- use exec to use exitcode (if any) directly from the tcl script
#exec /usr/bin/env tclsh "$0" "$@"
#-- alternative - can run sh/bash script after the tcl call.
/usr/bin/env tclsh "$0" "$@"
exitcode=$?
#echo "tcl exitcode: ${exitcode}"
#echo "sh/bash reporting tcl exitcode: ${exitcode}"
#-- override exitcode example
#exit 66
#</shell-launch-tcl>
#</shell-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<shell-payload-post-tcl>
#</shell-payload-post-tcl>
#<shell-post-launch-subprocess>
#</shell-post-launch-subprocess>
#printf "sh/bash done \n"
@ -448,7 +548,57 @@ exitcode=$?
#------------------------------------------------------
fi
exit ${exitcode}
# end hide sh/bash block from Tcl
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- Perl script section
# -- leave the script below as is, if all that is required is launching the Tcl payload"
# --
# -- Note that perl script isn't called by default when simply running this script by name
# -- adjust the nextshell value at the top of the script to point to perl
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
=cut
#!/user/bin/perl
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
my $exit_code = 0;
#use ExtUtils::Installed;
#my $installed = ExtUtils::Installed->new();
#my @modules = $installed->modules();
#print "Modules:\n";
#foreach my $m (@modules) {
# print "$m\n";
#}
# -- --- ---
my $scriptname = $0;
print "perl $scriptname\n";
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
}
#<perl-pre-launch-subprocess>
#</perl-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-launch-subprocess>
$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-post-launch-subprocess>
#</perl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload
exit $exit_code;
__END__
# end hide sh/bash/perl block from Tcl
# This comment with closing brace should stay in place whether if commented or not }
#------------------------------------------------------
# begin hide powershell-block from Tcl - only needed if Tcl didn't exit or return above
@ -460,9 +610,76 @@ if 0 {
# -- 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
$scriptname = GetScriptName
function GetDynamicParamDictionary {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true, Mandatory=$true)]
[string] $CommandName
)
begin {
# Get a list of params that should be ignored (they're common to all advanced functions)
$CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) |
Get-Member -MemberType Properties |
Select-Object -ExpandProperty Name
}
process {
# Create the dictionary that this scriptblock will return:
$DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# Convert to object array and get rid of Common params:
(Get-Command $CommandName | select -exp Parameters).GetEnumerator() |
Where-Object { $CommonParameterNames -notcontains $_.Key } |
ForEach-Object {
$DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter (
$_.Key,
$_.Value.ParameterType,
$_.Value.Attributes
)
$DynParamDictionary.Add($_.Key, $DynamicParameter)
}
# Return the dynamic parameters
return $DynParamDictionary
}
}
# GetDynamicParamDictionary
# - This can make it easier to share a single set of param definitions between functions
# - sample usage
#function ParameterDefinitions {
# param(
# [Parameter(Mandatory)][string] $myargument
# )
#}
#function psmain {
# [CmdletBinding()]
# param()
# dynamicparam { GetDynamicParamDictionary ParameterDefinitions }
# process {
# #called once with $PSBoundParameters dictionary
# #can be used to validate arguments, or set a simpler variable name for access
# switch ($PSBoundParameters.keys) {
# 'myargumentname' {
# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
# }
# #...
# }
# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
# #...
# }
# }
# end {
# #Main function logic
# Write-Host "myargumentname value is: $myargumentname"
# #myotherfunction @PSBoundParameters
# }
#}
#psmain @args
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
@ -470,22 +687,22 @@ $scriptname = getScriptName
#"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
#<powershell-payload-pre-tcl>
#</powershell-payload-pre-tcl>
#<powershell-pre-launch-subprocess>
#</powershell-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<powershell-launch-tcl>
#<powershell-launch-subprocess>
tclsh $scriptname $args
#</powershell-launch-tcl>
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<powershell-payload-post-tcl>
#</powershell-payload-post-tcl>
#<powershell-post-launch-subprocess>
#</powershell-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
Exit $LASTEXITCODE
# heredoc2 for powershell to ignore block below
$1 = @'
@ -498,7 +715,7 @@ $1 = @'
: \
@REM @ECHO exitcode: !task_exitcode!
: \
@IF "%1"=="PUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
: \
@EXIT /B !task_exitcode!
# cmd has exited
@ -509,6 +726,7 @@ $1 = @'
# -- powershell multiline comment
#>
<#
no script engine should try to run me
# id:tailblock1
# <ctrl-z>


240
src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell2.cmd

@ -1,34 +1,29 @@
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" +
: "[rename set s;proc Hide x {proc $x args {}};Hide :]" "\$(function : {<#pwsh#>})" ^
set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershell;proc Hide x {proc $x args {}}; 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 @ECHO; Hide ); Hide (;Hide echo; 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 \
: {
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT. 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.
@REM ############################################################################################################################
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, sh and/or powershelll (powershell.exe or pwsh.exe)
@REM It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################
@REM On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM e.g from within a running punkshell: pmix scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used)
@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh' (^14^) 'perl'"
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh'"
@SET "shells[10]=pwsh"
@SET "shells[11]=sh"
@set "shells[12]=bash"
@SET "shells[13]=tclsh"
@SET "shells[14]=perl"
: <nextshell>
@SET "nextshell=13"
: </nextshell>
@ -54,16 +49,16 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@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 -- pmix 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 -- 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 pmix 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 -- It is unknown what versions of cmd interpreters behave this way - and pmix 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
@ -94,40 +89,22 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$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" (
@IF "%1"=="PUNK-ELEVATED" (
GOTO :gotPrivileges
)
@IF !asadmin!==1 (
net file 1>NUL 2>NUL
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges )
)
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@GOTO skip_privileges
:getPrivileges
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@IF '%1'=='PUNK-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 UAC.ShellExecute "%~dp0%~n0.cmd", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script in new windows due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B
@ -136,7 +113,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@REM setlocal & pushd .
@PUSHD .
@cd /d %~dp0
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
@IF "%1"=="PUNK-ELEVATED" (
@DEL "%vbsGetPrivileges%" 1>nul 2>nul
@SET arglist=%arglist:~14%
)
@ -147,7 +124,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@if not exist "%~dp0%~n0.ps1" (
@SET need_ps1=1
) ELSE (
fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different
fc "%~dp0%~n0.cmd" "%~dp0%~n0.ps1" >nul || goto different
@REM @ECHO "files same"
@SET need_ps1=0
)
@ -157,7 +134,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@SET need_ps1=1
:pscontinue
@IF !need_ps1!==1 (
COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL
COPY "%~dp0%~n0.cmd" "%~dp0%~n0.ps1" >NUL
)
@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "!shells[%nextshell%]!"=="pwsh" (
@ -168,8 +145,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
SET task_exitcode=!errorlevel!
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% & SET task_exitcode=!errorlevel!
) ELSE (
REM CALL powershell -nop -nol -c write-host powershell-found
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %*
@ -180,26 +156,21 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
IF "!shells[%nextshell%]!"=="bash" (
CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%"
!shells[%nextshell%]! "!wslpath!%fname%" %arglist%
SET task_exitcode=!errorlevel!
!shells[%nextshell%]! "!wslpath!%fname%" %arglist% & SET task_exitcode=!errorlevel!
) ELSE (
REM probably tclsh or sh
IF NOT "x%keyRemoved%"=="x%validshells%" (
REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
!shells[%nextshell%]! "%~dp0%fname%" %arglist%
SET task_exitcode=!errorlevel!
!shells[%nextshell%]! "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel!
) ELSE (
ECHO %fname% has invalid nextshell value ^(%nextshell%^) !shells[%nextshell%]! valid options are %validshells%
SET task_exitcode=66
@REM boundary padding
@REM boundary padding
GOTO :exit_multishell
)
)
)
@REM batch file library functions
@REM boundary padding
@GOTO :endlib
:getWslPath
@ -256,7 +227,6 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
)
@EXIT /B
@REM boundary padding
@REM boundary padding
:getNormalizedScriptTail
@SETLOCAL
@SET "result=%~nx0"
@ -275,8 +245,6 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash
@ -412,15 +380,9 @@ namespace eval ::punk::multishell {
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-pre-launch-subprocess>
#</tcl-pre-launch-subprocess>
#<tcl-launch-subprocess>
#</tcl-launch-subproces>
#<tcl-payload>
#</tcl-payload>
#<tcl-post-launch-subprocess>
#</tcl-post-launch-subproces>
# -- --- --- --- --- --- --- --- --- --- --- ---
@ -452,33 +414,33 @@ if false==false # else {
# -- 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
# -- adjust @call line above ... to something like @call sh ... @call bash .. or @call env sh ... etc as appropriate
# -- if sh/bash scripting needs to run on windows too.
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
exitcode=0
#printf "start of bash or sh code"
#<shell-pre-launch-subprocess>
#</shell-pre-launch-subprocess>
#<shell-payload-pre-tcl>
#</shell-payload-pre-tcl>
# -- --- --- --- --- --- --- ---
#<shell-launch-subprocess>
#<shell-launch-tcl>
exitcode=0 ;#default assumption
#-- 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}"
#echo "tcl exitcode: ${exitcode}"
#-- override exitcode example
#exit 66
#</shell-launch-subprocess>
#</shell-launch-tcl>
# -- --- --- --- --- --- --- ---
#<shell-post-launch-subprocess>
#</shell-post-launch-subproces>
#<shell-payload-post-tcl>
#</shell-payload-post-tcl>
#printf "sh/bash done \n"
@ -486,57 +448,7 @@ exitcode=$?
#------------------------------------------------------
fi
exit ${exitcode}
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- Perl script section
# -- leave the script below as is, if all that is required is launching the Tcl payload"
# --
# -- Note that perl script isn't called by default when simply running this script by name
# -- adjust the nextshell value at the top of the script to point to perl
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
=cut
#!/user/bin/perl
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
my $exit_code = 0;
#use ExtUtils::Installed;
#my $installed = ExtUtils::Installed->new();
#my @modules = $installed->modules();
#print "Modules:\n";
#foreach my $m (@modules) {
# print "$m\n";
#}
# -- --- ---
my $scriptname = $0;
print "perl $scriptname\n";
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
}
#<perl-pre-launch-subprocess>
#</perl-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-launch-subprocess>
$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-post-launch-subprocess>
#</perl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload
exit $exit_code;
__END__
# end hide sh/bash/perl block from Tcl
# end hide sh/bash 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
@ -548,76 +460,9 @@ if 0 {
# -- Do not edit if current file is the .ps1
# -- Edit the corresponding .cmd and it will autocopy
# -- unbalanced braces { } here *even in comments* will cause problems if there was no Tcl exit or return above
# -- custom script should generally go below the begin_powershell_payload line
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
function GetScriptName { $myInvocation.ScriptName }
$scriptname = GetScriptName
function GetDynamicParamDictionary {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true, Mandatory=$true)]
[string] $CommandName
)
begin {
# Get a list of params that should be ignored (they're common to all advanced functions)
$CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) |
Get-Member -MemberType Properties |
Select-Object -ExpandProperty Name
}
process {
# Create the dictionary that this scriptblock will return:
$DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# Convert to object array and get rid of Common params:
(Get-Command $CommandName | select -exp Parameters).GetEnumerator() |
Where-Object { $CommonParameterNames -notcontains $_.Key } |
ForEach-Object {
$DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter (
$_.Key,
$_.Value.ParameterType,
$_.Value.Attributes
)
$DynParamDictionary.Add($_.Key, $DynamicParameter)
}
# Return the dynamic parameters
return $DynParamDictionary
}
}
# GetDynamicParamDictionary
# - This can make it easier to share a single set of param definitions between functions
# - sample usage
#function ParameterDefinitions {
# param(
# [Parameter(Mandatory)][string] $myargument
# )
#}
#function psmain {
# [CmdletBinding()]
# param()
# dynamicparam { GetDynamicParamDictionary ParameterDefinitions }
# process {
# #called once with $PSBoundParameters dictionary
# #can be used to validate arguments, or set a simpler variable name for access
# switch ($PSBoundParameters.keys) {
# 'myargumentname' {
# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
# }
# #...
# }
# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
# #...
# }
# }
# end {
# #Main function logic
# Write-Host "myargumentname value is: $myargumentname"
# #myotherfunction @PSBoundParameters
# }
#}
#psmain @args
$scriptname = getScriptName
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
@ -625,22 +470,22 @@ function GetDynamicParamDictionary {
#"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
#<powershell-pre-launch-subprocess>
#</powershell-pre-launch-subprocess>
#<powershell-payload-pre-tcl>
#</powershell-payload-pre-tcl>
# -- --- --- --- --- --- --- ---
#<powershell-launch-subprocess>
#<powershell-launch-tcl>
tclsh $scriptname $args
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
#</powershell-launch-tcl>
# -- --- --- --- --- --- --- ---
#<powershell-post-launch-subprocess>
#</powershell-post-launch-subprocess>
#<powershell-payload-post-tcl>
#</powershell-payload-post-tcl>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
Exit $LASTEXITCODE
# heredoc2 for powershell to ignore block below
$1 = @'
@ -653,7 +498,7 @@ $1 = @'
: \
@REM @ECHO exitcode: !task_exitcode!
: \
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
@IF "%1"=="PUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
: \
@EXIT /B !task_exitcode!
# cmd has exited
@ -664,7 +509,6 @@ $1 = @'
# -- powershell multiline comment
#>
<#
no script engine should try to run me
# id:tailblock1
# <ctrl-z>


680
src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell3.cmd

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

# </ctrl-z>
# -- unreachable by tcl directly if ctrl-z character is in the <ctrl-z> section above. (but file can be read and split on \x1A)
# -- Potential for zip and/or base64 contents, but we can't stop pwsh parser from slurping in the data
# -- so for example a plain text tar archive could cause problems depending on the content.
# -- final line in file must be the powershell multiline comment terminator or other data it can handle.
# -- e.g plain # comment lines will work too
# -- (for example a powershell digital signature is a # commented block of data at the end of the file)
#>

1
src/vfs/_vfscommon.vfs/modules/punk/pdf-0.1.0.tm

@ -1894,6 +1894,7 @@ tcl::namespace::eval punk::pdf::lib {
dict set map %marker% "[punk::ansi::a bold cyan]MERGEDBLOCK[punk::ansi::a]"
puts $outc [string map $map $opt_blocksep]
}
set blockresult ""
if {$opt_shrink_textfree_blocks} {
set teststripped [punk::ansi::ansistrip $mergedblock]
if {[string trim $teststripped] ne ""} {

Loading…
Cancel
Save