Browse Source

scriptwrap fixes

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

315
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
package require punk::mix::base package require punk::mix::base
package require punk::fileline package require punk::fileline
package require punk::ansi
#*** !doctools #*** !doctools
@ -78,6 +79,7 @@ namespace eval punk::mix::commandset::scriptwrap {
#[list_begin definitions] #[list_begin definitions]
namespace export {[a-z]*} namespace export {[a-z]*}
namespace import ::punk::ansi::a ::punk::ansi::a+
namespace eval fileline { namespace eval fileline {
namespace import ::punk::fileline::lib::* namespace import ::punk::fileline::lib::*
@ -807,7 +809,7 @@ namespace eval punk::mix::commandset::scriptwrap {
} }
return $result 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 #scriptset name to substitute multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
#set usage "" #set usage ""
#append usage "Use directly with the script file to wrap, or supply the name of a scriptset" \n #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}]} { if {[tomlish::dict::path::exists $tomldict {.application.template}]} {
dict set resultd template [tomlish::dict::path::get $tomldict {.application.template.value}] 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] set scripts [list]
if {[tomlish::dict::path::exists $tomldict {.application.scripts.value}]} { if {[tomlish::dict::path::exists $tomldict {.application.scripts.value}]} {
set arrvalues [tomlish::dict::path::get $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 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 { punk::args::define {
@id -id ::punk::mix::commandset::scriptwrap::multishell @id -id ::punk::mix::commandset::scriptwrap::multishell
@cmd -name 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 -returnextra -type boolean -default 0
@values -minvalues 0 -maxvalues 0 @values -minvalues 0 -maxvalues 0
} }
#: <nextshell> #: <<nextshell_start>>
#@SET "nextshellpath[win32___________]=tclsh___________________________" #@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________" #@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________" #@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________" #@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________" #@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________" #@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________" #@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________" #@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________" #@SET "nextshelltype[other___________]=tcl_____________"
#: </nextshell> #: <<nextshell_end>>
proc multishell {args} { proc multishell {args} {
set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell] set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell]
lassign [dict values $argd] leaders opts values received lassign [dict values $argd] leaders opts values received
@ -947,7 +1014,7 @@ namespace eval punk::mix::commandset::scriptwrap {
set startdir [pwd] set startdir [pwd]
set allowed_extensions [list tcl ps1 sh bash pl] set allowed_extensions [list tcl ps1 sh bash pl]
#TODO - distinct sections for sh vs bash? needs experiments.. #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 sh bash bash pl perl]
set extension_langs [list tcl tcl ps1 powershell sh shell bash shell 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 list_input_files [list]
set has_config 0
set configd [dict create] set configd [dict create]
if {$scriptset ne ""} { if {$scriptset ne ""} {
puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'" puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'"
#.toml file may or may not exist #.toml file may or may not exist
if {[file exists ${scriptset}_wrap.toml]} { 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 configd [_read_scriptset_wrap_tomlfile $scriptdir/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} { if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts] set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts { foreach s $configured_scripts {
@ -1008,6 +1077,7 @@ namespace eval punk::mix::commandset::scriptwrap {
return false return false
} }
} else { } else {
set configd [_default_configd]
puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml" puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml"
puts stdout "Will look for the following scripts in $scriptdir" puts stdout "Will look for the following scripts in $scriptdir"
foreach e $allowed_extensions { foreach e $allowed_extensions {
@ -1018,12 +1088,17 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptdir/$scriptset.$e lappend list_input_files $scriptdir/$scriptset.$e
} }
} }
dict set configd scripts $list_input_files
} }
} else { } else {
set configd [_default_configd]
#expect a single script #expect a single script
if {[file exists $specified_path]} { if {[file exists $specified_path]} {
lappend list_input_files $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}] set found_script [expr {[llength $list_input_files] > 0}]
@ -1061,8 +1136,9 @@ namespace eval punk::mix::commandset::scriptwrap {
if {$scriptset ne ""} { if {$scriptset ne ""} {
#.toml file may or may not exist #.toml file may or may not exist
if {[file exists $scriptroot/${scriptset}_wrap.toml]} { 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 configd [_read_scriptset_wrap_tomlfile $scriptroot/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} { if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts] set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts { foreach s $configured_scripts {
@ -1084,6 +1160,8 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptroot/$scriptset.$e lappend list_input_files $scriptroot/$scriptset.$e
} }
} }
set configd [_default_configd]
dict set configd scripts [lmap f $list_input_files {file tail $f}]
} }
} else { } else {
#expect a single script #expect a single script
@ -1094,6 +1172,8 @@ namespace eval punk::mix::commandset::scriptwrap {
} }
lappend list_input_files $scriptroot/$filepath_or_scriptset 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}] 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 #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] set templatename [dict get $configd template]
} else { } else {
if {$opt_template eq "\uFFFF"} { if {$opt_template eq "\uFFFF"} {
@ -1119,6 +1199,7 @@ namespace eval punk::mix::commandset::scriptwrap {
} else { } else {
set templatename $opt_template set templatename $opt_template
} }
dict set configd template $templatename
} }
set templatename_root [file rootname [file tail $templatename]] set templatename_root [file rootname [file tail $templatename]]
@ -1199,21 +1280,26 @@ namespace eval punk::mix::commandset::scriptwrap {
#todo #todo
#output_file extension may also depend on the template being used.. and/or the <scriptset>_wrap.toml config #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
package require platform set thisplatform [string tolower [platform::identify]]
set thisplatform [string tolower [platform::identify]] set ptype [lindex [split $thisplatform -] 0]
set ptype [lindex [split $thisplatform -] 0] switch -- $ptype {
switch -- $ptype { win32 - dragonflybsd - freebsd - netbsd - linux - macosx {}
win32 - dragonflybsd - freebsd - netbsd - linux - macosx {} default {
default { set ptype other
set ptype other
}
} }
}
if {$has_config} {
set out [dict get $configd $ptype outputfile] set out [dict get $configd $ptype outputfile]
set output_file [file join $output_folder $out] 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 { } else {
#no _wrap.toml file available #no _wrap.toml file available
if {$::tcl_platform(platform) eq "windows"} { if {$ptype eq "win32"} {
set output_extension .cmd set output_extension .cmd
} else { } else {
set output_extension .sh set output_extension .sh
@ -1224,6 +1310,11 @@ namespace eval punk::mix::commandset::scriptwrap {
set infile [lindex $list_input_files 0] set infile [lindex $list_input_files 0]
set output_file [file join $output_folder [file rootname [file tail $infile]]$output_extension] 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]" 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_force} {
if {$opt_askme} { 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"} { 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." 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 $objFile_existing destroy
@ -1264,92 +1355,122 @@ namespace eval punk::mix::commandset::scriptwrap {
puts stdout $ln puts stdout $ln
} }
puts stdout "-----------------------------------------------\n" 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 lang_data [dict create]
set filepath [lindex $list_input_files 0] foreach filepath $list_input_files {
set fdscript [open $filepath r] set script_ext [string trim [file extension $filepath] .]
fconfigure $fdscript -translation binary set lang [dict get $extension_langs [string tolower $script_ext]]
set script_data [read $fdscript] set fdscript [open $filepath r]
close $fdscript fconfigure $fdscript -translation binary
puts stdout "Read [string length $script_data] bytes of template data.." set script_data [read $fdscript]
set script_lines [split $script_data \n] close $fdscript
puts stdout "Displaying first 3 lines of your script between dashed lines..." puts stdout "Read [string length $script_data] bytes of template data for lang: $lang"
puts stdout "-----------------------------------------------" set script_lines [split $script_data \n]
foreach ln [lrange $script_lines 0 3] { dict set lang_data $lang $script_lines
puts stdout $ln puts stdout "Displaying first 3 lines of your script [file tail $filepath] between dashed lines..."
} puts stdout "[a green]$filepath[a]"
puts stdout "-----------------------------------------------\n" puts stdout "-----------------------------------------------"
puts stdout "Target for above script data is '$output_file'" foreach ln [lrange $script_lines 0 3] {
set script_ext [string trim [file extension $filepath] .] puts stdout $ln
set lang [dict get $extension_langs [string tolower $script_ext]] }
puts stdout "Language of script being wrapped is $lang" puts stdout "-----------------------------------------------\n"
if {$opt_askme} { puts stdout "Target for script data is '$output_file'"
set answer [util::askuser "Does this look correct? Y|N"] puts stdout "Language of script being wrapped is [a bold yellow]$lang[a]"
if {[string tolower $answer] ne "y"} { if {$opt_askme} {
puts stderr "mix new aborting due to user response '$answer' (required Y or y to proceed) use -askme 0 to avoid prompts." set answer [util::askuser "Does this look correct? Y|N"]
return if {[string tolower $answer] ne "y"} {
puts stderr "mix new aborting due to user response '$answer' (required Y or y to proceed) use -askme 0 to avoid prompts."
return
}
} }
} }
set start_idx 0 set template_ranges [list]
set end_idx 0 set data_items [list]
set line_idx 0 set trange [list 0]
set existing_payload [list] set line_idx -1
set opentag ""
foreach ln $template_lines { foreach ln $template_lines {
incr line_idx
if {[string match "#<$lang-pre-launch-subprocess>*" $ln]} { if {$opentag eq ""} {
set start_idx $line_idx if {[string match ": <<asadmin_start>>*" $ln]} {
} elseif {[string match "#</$lang-pre-launch-subprocess>*" $ln]} { set opentag asadmin
set end_idx $line_idx lset trange 1 $line_idx ;#include tag in template
break lappend template_ranges $trange
} elseif {$start_idx > 0} { set trange [list $line_idx]
if {$end_idx > 0} { set asadmin [dict get $configd as_admin]
lappend existing_payload [string trim $ln] 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 { } else {
switch -- [string range $opentag 0 6] {
asadmin {
if {[string match ": <<asadmin_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
nextshe {
if {[string match ": <<nextshell_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
payload {
set lang [string range $opentag 8 end] ;#payload-xxx
if {[string match "#</$lang-payload>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
}
} }
incr line_idx
} }
if {($start_idx == 0) || ($end_idx == 0)} { if {$opentag eq ""} {
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" lset trange 1 end
lappend template_ranges $trange
} else {
error "multishell - unable to find closing tag for '$opentag'"
} }
set existing_string [join $existing_payload \n] set newscript ""
if {[string length [string trim $existing_string]]} { foreach trange $template_ranges item $data_items {
puts stdout "EXISTING <$lang-pre-launch-subprocess> PAYLOAD!!" append newscript [join [lrange $template_lines {*}$trange] \n]
puts stdout "-----------------------------------------------\n" if {$item ne ""} {
puts stdout $existing_string append newscript \n $item \n
puts stdout "-----------------------------------------------\n" } else {
error "wrap_in_multishell found existing payload for language $lang ... aborting." append newscript \n
#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
}
}
} }
} }
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 "New script is [string length $newscript] bytes"
puts stdout $newscript puts stdout $newscript
set fdtarget [open $output_file w] set fdtarget [open $output_file w]
fconfigure $fdtarget -translation binary fconfigure $fdtarget -translation binary
puts -nonewline $fdtarget $newscript puts -nonewline $fdtarget $newscript

2
src/bootsupport/modules/punk/zip-0.1.1.tm

@ -400,7 +400,7 @@ tcl::namespace::eval punk::zip {
#todo - if it was internal preamble - need to adjust offsets to fix the split off zipfile #todo - if it was internal preamble - need to adjust offsets to fix the split off zipfile
set zout [open $outfile_zip w] set zout [open $outfile_zip w]
fconfigure $zout -encoding iso8859-1 -translation binary fconfigure $zout -encoding iso8859-1 -translation binary
chan copy $inzip $zout chan copy $inzip $zout
close $zout close $zout
} }
close $inzip close $inzip

315
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
package require punk::mix::base package require punk::mix::base
package require punk::fileline package require punk::fileline
package require punk::ansi
#*** !doctools #*** !doctools
@ -78,6 +79,7 @@ namespace eval punk::mix::commandset::scriptwrap {
#[list_begin definitions] #[list_begin definitions]
namespace export {[a-z]*} namespace export {[a-z]*}
namespace import ::punk::ansi::a ::punk::ansi::a+
namespace eval fileline { namespace eval fileline {
namespace import ::punk::fileline::lib::* namespace import ::punk::fileline::lib::*
@ -807,7 +809,7 @@ namespace eval punk::mix::commandset::scriptwrap {
} }
return $result 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 #scriptset name to substitute multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
#set usage "" #set usage ""
#append usage "Use directly with the script file to wrap, or supply the name of a scriptset" \n #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}]} { if {[tomlish::dict::path::exists $tomldict {.application.template}]} {
dict set resultd template [tomlish::dict::path::get $tomldict {.application.template.value}] 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] set scripts [list]
if {[tomlish::dict::path::exists $tomldict {.application.scripts.value}]} { if {[tomlish::dict::path::exists $tomldict {.application.scripts.value}]} {
set arrvalues [tomlish::dict::path::get $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 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 { punk::args::define {
@id -id ::punk::mix::commandset::scriptwrap::multishell @id -id ::punk::mix::commandset::scriptwrap::multishell
@cmd -name 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 -returnextra -type boolean -default 0
@values -minvalues 0 -maxvalues 0 @values -minvalues 0 -maxvalues 0
} }
#: <nextshell> #: <<nextshell_start>>
#@SET "nextshellpath[win32___________]=tclsh___________________________" #@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________" #@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________" #@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________" #@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________" #@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________" #@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________" #@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________" #@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________" #@SET "nextshelltype[other___________]=tcl_____________"
#: </nextshell> #: <<nextshell_end>>
proc multishell {args} { proc multishell {args} {
set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell] set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell]
lassign [dict values $argd] leaders opts values received lassign [dict values $argd] leaders opts values received
@ -947,7 +1014,7 @@ namespace eval punk::mix::commandset::scriptwrap {
set startdir [pwd] set startdir [pwd]
set allowed_extensions [list tcl ps1 sh bash pl] set allowed_extensions [list tcl ps1 sh bash pl]
#TODO - distinct sections for sh vs bash? needs experiments.. #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 sh bash bash pl perl]
set extension_langs [list tcl tcl ps1 powershell sh shell bash shell 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 list_input_files [list]
set has_config 0
set configd [dict create] set configd [dict create]
if {$scriptset ne ""} { if {$scriptset ne ""} {
puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'" puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'"
#.toml file may or may not exist #.toml file may or may not exist
if {[file exists ${scriptset}_wrap.toml]} { 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 configd [_read_scriptset_wrap_tomlfile $scriptdir/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} { if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts] set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts { foreach s $configured_scripts {
@ -1008,6 +1077,7 @@ namespace eval punk::mix::commandset::scriptwrap {
return false return false
} }
} else { } else {
set configd [_default_configd]
puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml" puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml"
puts stdout "Will look for the following scripts in $scriptdir" puts stdout "Will look for the following scripts in $scriptdir"
foreach e $allowed_extensions { foreach e $allowed_extensions {
@ -1018,12 +1088,17 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptdir/$scriptset.$e lappend list_input_files $scriptdir/$scriptset.$e
} }
} }
dict set configd scripts $list_input_files
} }
} else { } else {
set configd [_default_configd]
#expect a single script #expect a single script
if {[file exists $specified_path]} { if {[file exists $specified_path]} {
lappend list_input_files $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}] set found_script [expr {[llength $list_input_files] > 0}]
@ -1061,8 +1136,9 @@ namespace eval punk::mix::commandset::scriptwrap {
if {$scriptset ne ""} { if {$scriptset ne ""} {
#.toml file may or may not exist #.toml file may or may not exist
if {[file exists $scriptroot/${scriptset}_wrap.toml]} { 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 configd [_read_scriptset_wrap_tomlfile $scriptroot/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} { if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts] set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts { foreach s $configured_scripts {
@ -1084,6 +1160,8 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptroot/$scriptset.$e lappend list_input_files $scriptroot/$scriptset.$e
} }
} }
set configd [_default_configd]
dict set configd scripts [lmap f $list_input_files {file tail $f}]
} }
} else { } else {
#expect a single script #expect a single script
@ -1094,6 +1172,8 @@ namespace eval punk::mix::commandset::scriptwrap {
} }
lappend list_input_files $scriptroot/$filepath_or_scriptset 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}] 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 #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] set templatename [dict get $configd template]
} else { } else {
if {$opt_template eq "\uFFFF"} { if {$opt_template eq "\uFFFF"} {
@ -1119,6 +1199,7 @@ namespace eval punk::mix::commandset::scriptwrap {
} else { } else {
set templatename $opt_template set templatename $opt_template
} }
dict set configd template $templatename
} }
set templatename_root [file rootname [file tail $templatename]] set templatename_root [file rootname [file tail $templatename]]
@ -1199,21 +1280,26 @@ namespace eval punk::mix::commandset::scriptwrap {
#todo #todo
#output_file extension may also depend on the template being used.. and/or the <scriptset>_wrap.toml config #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
package require platform set thisplatform [string tolower [platform::identify]]
set thisplatform [string tolower [platform::identify]] set ptype [lindex [split $thisplatform -] 0]
set ptype [lindex [split $thisplatform -] 0] switch -- $ptype {
switch -- $ptype { win32 - dragonflybsd - freebsd - netbsd - linux - macosx {}
win32 - dragonflybsd - freebsd - netbsd - linux - macosx {} default {
default { set ptype other
set ptype other
}
} }
}
if {$has_config} {
set out [dict get $configd $ptype outputfile] set out [dict get $configd $ptype outputfile]
set output_file [file join $output_folder $out] 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 { } else {
#no _wrap.toml file available #no _wrap.toml file available
if {$::tcl_platform(platform) eq "windows"} { if {$ptype eq "win32"} {
set output_extension .cmd set output_extension .cmd
} else { } else {
set output_extension .sh set output_extension .sh
@ -1224,6 +1310,11 @@ namespace eval punk::mix::commandset::scriptwrap {
set infile [lindex $list_input_files 0] set infile [lindex $list_input_files 0]
set output_file [file join $output_folder [file rootname [file tail $infile]]$output_extension] 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]" 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_force} {
if {$opt_askme} { 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"} { 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." 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 $objFile_existing destroy
@ -1264,92 +1355,122 @@ namespace eval punk::mix::commandset::scriptwrap {
puts stdout $ln puts stdout $ln
} }
puts stdout "-----------------------------------------------\n" 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 lang_data [dict create]
set filepath [lindex $list_input_files 0] foreach filepath $list_input_files {
set fdscript [open $filepath r] set script_ext [string trim [file extension $filepath] .]
fconfigure $fdscript -translation binary set lang [dict get $extension_langs [string tolower $script_ext]]
set script_data [read $fdscript] set fdscript [open $filepath r]
close $fdscript fconfigure $fdscript -translation binary
puts stdout "Read [string length $script_data] bytes of template data.." set script_data [read $fdscript]
set script_lines [split $script_data \n] close $fdscript
puts stdout "Displaying first 3 lines of your script between dashed lines..." puts stdout "Read [string length $script_data] bytes of template data for lang: $lang"
puts stdout "-----------------------------------------------" set script_lines [split $script_data \n]
foreach ln [lrange $script_lines 0 3] { dict set lang_data $lang $script_lines
puts stdout $ln puts stdout "Displaying first 3 lines of your script [file tail $filepath] between dashed lines..."
} puts stdout "[a green]$filepath[a]"
puts stdout "-----------------------------------------------\n" puts stdout "-----------------------------------------------"
puts stdout "Target for above script data is '$output_file'" foreach ln [lrange $script_lines 0 3] {
set script_ext [string trim [file extension $filepath] .] puts stdout $ln
set lang [dict get $extension_langs [string tolower $script_ext]] }
puts stdout "Language of script being wrapped is $lang" puts stdout "-----------------------------------------------\n"
if {$opt_askme} { puts stdout "Target for script data is '$output_file'"
set answer [util::askuser "Does this look correct? Y|N"] puts stdout "Language of script being wrapped is [a bold yellow]$lang[a]"
if {[string tolower $answer] ne "y"} { if {$opt_askme} {
puts stderr "mix new aborting due to user response '$answer' (required Y or y to proceed) use -askme 0 to avoid prompts." set answer [util::askuser "Does this look correct? Y|N"]
return if {[string tolower $answer] ne "y"} {
puts stderr "mix new aborting due to user response '$answer' (required Y or y to proceed) use -askme 0 to avoid prompts."
return
}
} }
} }
set start_idx 0 set template_ranges [list]
set end_idx 0 set data_items [list]
set line_idx 0 set trange [list 0]
set existing_payload [list] set line_idx -1
set opentag ""
foreach ln $template_lines { foreach ln $template_lines {
incr line_idx
if {[string match "#<$lang-pre-launch-subprocess>*" $ln]} { if {$opentag eq ""} {
set start_idx $line_idx if {[string match ": <<asadmin_start>>*" $ln]} {
} elseif {[string match "#</$lang-pre-launch-subprocess>*" $ln]} { set opentag asadmin
set end_idx $line_idx lset trange 1 $line_idx ;#include tag in template
break lappend template_ranges $trange
} elseif {$start_idx > 0} { set trange [list $line_idx]
if {$end_idx > 0} { set asadmin [dict get $configd as_admin]
lappend existing_payload [string trim $ln] 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 { } else {
switch -- [string range $opentag 0 6] {
asadmin {
if {[string match ": <<asadmin_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
nextshe {
if {[string match ": <<nextshell_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
payload {
set lang [string range $opentag 8 end] ;#payload-xxx
if {[string match "#</$lang-payload>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
}
} }
incr line_idx
} }
if {($start_idx == 0) || ($end_idx == 0)} { if {$opentag eq ""} {
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" lset trange 1 end
lappend template_ranges $trange
} else {
error "multishell - unable to find closing tag for '$opentag'"
} }
set existing_string [join $existing_payload \n] set newscript ""
if {[string length [string trim $existing_string]]} { foreach trange $template_ranges item $data_items {
puts stdout "EXISTING <$lang-pre-launch-subprocess> PAYLOAD!!" append newscript [join [lrange $template_lines {*}$trange] \n]
puts stdout "-----------------------------------------------\n" if {$item ne ""} {
puts stdout $existing_string append newscript \n $item \n
puts stdout "-----------------------------------------------\n" } else {
error "wrap_in_multishell found existing payload for language $lang ... aborting." append newscript \n
#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
}
}
} }
} }
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 "New script is [string length $newscript] bytes"
puts stdout $newscript puts stdout $newscript
set fdtarget [open $output_file w] set fdtarget [open $output_file w]
fconfigure $fdtarget -translation binary fconfigure $fdtarget -translation binary
puts -nonewline $fdtarget $newscript 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^ : "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 = @' set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \ : 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 + \ : .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" + : "[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 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 It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################ @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 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 sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system @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: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath> @REM e.g from within a running punkshell: dev 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 Call with sh, bash, perl, or tclsh. (powershell untested on unix)
@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 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 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 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 @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 @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 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 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 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 @REM Supporting more explicit oses than those listed may also require script padding adjustment
: <nextshell> : <<nextshell_start>>
@SET "nextshellpath[win32___________]=tclsh___________________________" @SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
@SET "nextshelltype[win32___________]=tcl_____________" @SET "nextshelltype[win32___________]=tcl_____________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________" @SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[dragonflybsd____]=tcl_____________" @SET "nextshelltype[dragonflybsd____]=tcl_____________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________" @SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[freebsd_________]=tcl_____________" @SET "nextshelltype[freebsd_________]=tcl_____________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________" @SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[netbsd__________]=tcl_____________" @SET "nextshelltype[netbsd__________]=tcl_____________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________" @SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[linux___________]=tcl_____________" @SET "nextshelltype[linux___________]=tcl_____________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________" @SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[macosx__________]=tcl_____________" @SET "nextshelltype[macosx__________]=tcl_____________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________" @SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[other___________]=tcl_____________" @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). @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" @SET "asadmin=0"
: </asadmin> : <<asadmin_end>>
@REM @ECHO nextshelltype is %nextshelltype[win32___________]% @REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM @SET "selected_shelltype=%nextshelltype[win32___________]%" @REM @SET "selected_shelltype=%nextshelltype[win32___________]%"
@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 args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%" @ECHO Next >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%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%" %* @"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B @EXIT /B
@ -175,8 +175,11 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
COPY "%~dp0%~n0%~x0" "%~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 /? @REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "%selected_shelltype_trimmed%"=="powershell" ( @IF "!selected_shelltype_trimmed!"=="none" (
REM pws vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time 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 REM test availability of preferred option of powershell7+ pwsh
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL
SET pwshtest_exitcode=!errorlevel! SET pwshtest_exitcode=!errorlevel!
@ -192,7 +195,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
SET task_exitcode=!errorlevel! SET task_exitcode=!errorlevel!
) )
) ELSE ( ) ELSE (
IF "%selected_shelltype_trimmed%"=="wslbash" ( IF "!selected_shelltype_trimmed!"=="wslbash" (
CALL :getWslPath %winpath% wslpath CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%" REM ECHO wslfullpath "!wslpath!%fname%"
%selected_shellpath_trimmed% "!wslpath!%fname%" %arglist% %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 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 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 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; %selected_shellpath_trimmed% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
) ELSE ( ) ELSE (
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes% 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 "rtrn=%~2"
@SET "string=%~1" @SET "string=%~1"
@SET "trimstring=%~1" @SET "trimstring=%~1"
@REM trim up to 31 underscores from the end of a string using string substitution @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 trimstring=%trimstring:__###=###% @SET "trimstring=%trimstring:____###=###%"
@SET trimstring=%trimstring:_###=###% @SET "trimstring=%trimstring:__###=###%"
@SET trimstring=%trimstring:###=% @SET "trimstring=%trimstring:_###=###%"
@SET "trimstring=%trimstring:###=%"
@SET "result=!trimstring!" @SET "result=!trimstring!"
@ENDLOCAL & ( @ENDLOCAL & (
@IF "%~2" neq "" ( @IF "%~2" neq "" (
@ -439,7 +444,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
# -- e.g tclsh filename.cmd # -- 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 '@ Hide :exit_multishell;Hide {<#};Hide '@
namespace eval ::punk::multishell { namespace eval ::punk::multishell {
set last_script_root [file dirname [file normalize ${::argv0}/__]] set last_script_root [file dirname [file normalize ${::argv0}/__]]
@ -473,6 +478,9 @@ namespace eval ::punk::multishell {
#puts "argv0 : $::argv0" #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>
#</tcl-pre-launch-subprocess> #</tcl-pre-launch-subprocess>
@ -502,8 +510,20 @@ if {[::punk::multishell::is_main]} {
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload # -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload
# end hide from unix shells \ # end hide from unix shells \
HEREDOC1B_HIDE_FROM_BASH_AND_SH 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 \ # sh/bash \
shift && set -- "${@:1:$#-1}" 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. # -- This if block only needed if Tcl didn't exit or return above.
if false==false # else { if false==false # else {
@ -518,10 +538,80 @@ if false==false # else {
# -- if sh/bash scripting needs to run on windows too. # -- 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 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" #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>
#</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 #-- use exec to use exitcode (if any) directly from the tcl script
#exec /usr/bin/env tclsh "$0" "$@" #exec /usr/bin/env tclsh "$0" "$@"
#-- alternative - can run sh/bash script after the tcl call. #-- alternative - can run sh/bash script after the tcl call.
/usr/bin/env tclsh "$0" "$@" #/usr/bin/env tclsh "$0" "$@"
exitcode=$? #exitcode=$?
#echo "sh/bash reporting tcl exitcode: ${exitcode}" #echo "sh/bash reporting tcl exitcode: ${exitcode}"
#-- override exitcode example #-- override exitcode example
#exit 66 #exit 66
@ -558,8 +648,18 @@ exit ${exitcode}
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### # ## ### ### ### ### ### ### ### ### ### ### ### ### ###
=cut =cut
#!/user/bin/perl #!/user/bin/perl
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
my $exit_code = 0; 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; #use ExtUtils::Installed;
#my $installed = ExtUtils::Installed->new(); #my $installed = ExtUtils::Installed->new();
#my @modules = $installed->modules(); #my @modules = $installed->modules();
@ -571,13 +671,15 @@ my $exit_code = 0;
my $scriptname = $0;
print "perl $scriptname\n";
my $i =1; my $i =1;
foreach my $a(@ARGV) { foreach my $a(@ARGV) {
print "Arg # $i: $a\n"; 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>
#</perl-pre-launch-subprocess> #</perl-pre-launch-subprocess>
@ -585,7 +687,7 @@ foreach my $a(@ARGV) {
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<perl-launch-subprocess> #<perl-launch-subprocess>
$exit_code=system("tclsh", $scriptname, @ARGV); #$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code"; #print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess> #</perl-launch-subprocess>
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
@ -680,12 +782,34 @@ function GetDynamicParamDictionary {
# } # }
#} #}
#psmain @args #psmain @args
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host #"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host #"Script Name : {0}" -f $scriptname | write-host
#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host #"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host
#"powershell args : {0}" -f ($args -join ", ") | 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>
#</powershell-pre-launch-subprocess> #</powershell-pre-launch-subprocess>
@ -693,7 +817,7 @@ function GetDynamicParamDictionary {
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<powershell-launch-subprocess> #<powershell-launch-subprocess>
tclsh $scriptname $args #tclsh $scriptname $args
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host #"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess> #</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#>})" ^ : "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=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershell;proc Hide x {proc $x args {}}; Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @' set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. (close sqote for unix shells) ' \ : 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 \ : .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" : "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" +
: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH' : << '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 \ : 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 ############################################################################################################################
@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 It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################ @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 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 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 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 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 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 @SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh'" @SET "validshelltypes= powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________"
@SET "shells[10]=pwsh" @REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch
@SET "shells[11]=sh" @REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx
@set "shells[12]=bash" @REM The horrible underscore-padded fixed-widths are to keep the batch labels aligned whilst allowing values to be set
@SET "shells[13]=tclsh" @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> : <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> : </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). @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>
@SET "asadmin=0" @SET "asadmin=0"
: </asadmin> : </asadmin>
@REM nextshell set to index for validshells .eg 10 for pwsh @REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM @ECHO nextshell is %nextshell% @REM @SET "selected_shelltype=%nextshelltype[win32___________]%"
@SET "selected=!shells[%nextshell%]!" @SET "selected_shelltype=%nextshelltype[win32___________]%"
@REM @ECHO selected %selected% @REM @ECHO selected_shelltype %selected_shelltype%
@CALL SET "keyRemoved=%%validshells:'!selected!'=%%" @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 @ECHO keyremoved %keyRemoved%
@REM Note that 'powershell' e.g v5 is just a fallback for when pwsh is not available @REM Note that 'powershell' e.g v5 is just a fallback for when pwsh is not available
@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ### @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 -- 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 -- Even something as simple as adding or removing an @REM
@REM -- From within punkshell - use: @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 -- to check your templates or final wrapped scripts for byte boundary issues
@REM -- It will report any labels that are on boundaries @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 -- 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 -- 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 -- 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 -- '@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 -- 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 -- 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 ############################################################################################################################
@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections @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 "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs"
@SET arglist=%* @SET arglist=%*
@IF "%1"=="PUNK-ELEVATED" ( @SET "qstrippedargs=args%arglist%"
@SET "qstrippedargs=%qstrippedargs:"=%"
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
GOTO :gotPrivileges GOTO :gotPrivileges
) )
@IF !asadmin!==1 ( @IF !asadmin!==1 (
net file 1>NUL 2>NUL net file 1>NUL 2>NUL
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges ) @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 @GOTO skip_privileges
:getPrivileges :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 Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%"
@ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%" @ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%"
@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%" @ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%" @ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%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 @ECHO Launching script in new windows due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %* @"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B @EXIT /B
@ -113,7 +151,7 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@REM setlocal & pushd . @REM setlocal & pushd .
@PUSHD . @PUSHD .
@cd /d %~dp0 @cd /d %~dp0
@IF "%1"=="PUNK-ELEVATED" ( @IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
@DEL "%vbsGetPrivileges%" 1>nul 2>nul @DEL "%vbsGetPrivileges%" 1>nul 2>nul
@SET arglist=%arglist:~14% @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" ( @if not exist "%~dp0%~n0.ps1" (
@SET need_ps1=1 @SET need_ps1=1
) ELSE ( ) 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" @REM @ECHO "files same"
@SET need_ps1=0 @SET need_ps1=0
) )
@ -134,10 +172,10 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@SET need_ps1=1 @SET need_ps1=1
:pscontinue :pscontinue
@IF !need_ps1!==1 ( @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 /? @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 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 REM test availability of preferred option of powershell7+ pwsh
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL 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 ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 ( 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 ( ) ELSE (
REM CALL powershell -nop -nol -c write-host powershell-found REM CALL powershell -nop -nol -c write-host powershell-found
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %* 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! SET task_exitcode=!errorlevel!
) )
) ELSE ( ) ELSE (
IF "!shells[%nextshell%]!"=="bash" ( IF "%selected_shelltype_trimmed%"=="wslbash" (
CALL :getWslPath %winpath% wslpath CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%" 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 ( ) ELSE (
REM probably tclsh or sh REM perl or tcl or sh or bash
IF NOT "x%keyRemoved%"=="x%validshells%" ( 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 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 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 ( ) 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 SET task_exitcode=66
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
GOTO :exit_multishell GOTO :exit_multishell
) )
) )
) )
@REM batch file library functions @REM batch file library functions
@REM boundary padding
@GOTO :endlib @GOTO :endlib
:getWslPath :getWslPath
@ -179,7 +225,9 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@SET "name=%~nx1" @SET "name=%~nx1"
@SET "drive=%~d1" @SET "drive=%~d1"
@SET "rtrn=%~2" @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 & ( @ENDLOCAL & (
@if "%~2" neq "" ( @if "%~2" neq "" (
SET "%rtrn%=%result%" SET "%rtrn%=%result%"
@ -227,6 +275,7 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
) )
@EXIT /B @EXIT /B
@REM boundary padding @REM boundary padding
@REM boundary padding
:getNormalizedScriptTail :getNormalizedScriptTail
@SETLOCAL @SETLOCAL
@SET "result=%~nx0" @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 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
@REM boundary padding
@REM boundary padding
@SETLOCAL @SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash @CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash @CALL :stringContains %~1 "/" hasForwardSlash
@ -289,7 +340,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
) )
) )
@EXIT /B @EXIT /B
@REM boundary padding
@REM boundary padding
:stringToUpper :stringToUpper
@SETLOCAL @SETLOCAL
@SET "rtrn=%~2" @SET "rtrn=%~2"
@ -307,7 +359,47 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
) )
) )
@EXIT /B @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 :isNumeric
@SETLOCAL @SETLOCAL
@SET "notnumeric="&FOR /F "delims=0123456789" %%i in ("%1") do set "notnumeric=%%i" @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 :endlib
: \ : \
@REM padding
@REM padding
@REM @SET taskexit_code=!errorlevel! & goto :exit_multishell @REM @SET taskexit_code=!errorlevel! & goto :exit_multishell
@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 rename set ""; rename s set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@ Hide :exit_multishell;Hide {<#};Hide '@
namespace eval ::punk::multishell { 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]/__]] set last_script [file dirname [file normalize [info script]/__]]
if {[info exists argv0] && if {[info exists ::argv0] &&
$last_script eq $last_script_root $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 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)]} { if {![info exists ::punk::multishell::is_main($script_name)]} {
#e.g a .dll or something else unanticipated #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 "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 0
} }
return [set ::punk::multishell::is_main($script_name)] return [set ::punk::multishell::is_main($script_name)]
@ -380,10 +474,16 @@ namespace eval ::punk::multishell {
# -- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-payload> #<tcl-pre-launch-subprocess>
#</tcl-payload> #</tcl-pre-launch-subprocess>
#<tcl-launch-subprocess>
#</tcl-launch-subprocess>
#<tcl-post-launch-subprocess>
#</tcl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- ---
# -- Best practice is to always return or exit above, or just by leaving the below defaults in place. # -- Best practice is to always return or exit above, or just by leaving the below defaults in place.
@ -414,33 +514,33 @@ if false==false # else {
# -- leave as is if all that is required is launching the Tcl payload" # -- 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 # -- 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. # -- if sh/bash scripting needs to run on windows too.
# -- # --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### # ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload # -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
exitcode=0
#printf "start of bash or sh code" #printf "start of bash or sh code"
#<shell-payload-pre-tcl> #<shell-pre-launch-subprocess>
#</shell-payload-pre-tcl> #</shell-pre-launch-subprocess>
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<shell-launch-tcl> #<shell-launch-subprocess>
exitcode=0 ;#default assumption
#-- sh/bash launches Tcl here instead of shebang line at top #-- sh/bash launches Tcl here instead of shebang line at top
#-- use exec to use exitcode (if any) directly from the tcl script #-- use exec to use exitcode (if any) directly from the tcl script
#exec /usr/bin/env tclsh "$0" "$@" #exec /usr/bin/env tclsh "$0" "$@"
#-- alternative - can run sh/bash script after the tcl call. #-- alternative - can run sh/bash script after the tcl call.
/usr/bin/env tclsh "$0" "$@" /usr/bin/env tclsh "$0" "$@"
exitcode=$? exitcode=$?
#echo "tcl exitcode: ${exitcode}" #echo "sh/bash reporting tcl exitcode: ${exitcode}"
#-- override exitcode example #-- override exitcode example
#exit 66 #exit 66
#</shell-launch-tcl> #</shell-launch-subprocess>
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<shell-payload-post-tcl> #<shell-post-launch-subprocess>
#</shell-payload-post-tcl> #</shell-post-launch-subprocess>
#printf "sh/bash done \n" #printf "sh/bash done \n"
@ -448,7 +548,57 @@ exitcode=$?
#------------------------------------------------------ #------------------------------------------------------
fi fi
exit ${exitcode} 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 } # 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 # 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 # -- Do not edit if current file is the .ps1
# -- Edit the corresponding .cmd and it will autocopy # -- 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 # -- 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 } 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 # -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host #"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host #"Script Name : {0}" -f $scriptname | write-host
@ -470,22 +687,22 @@ $scriptname = getScriptName
#"powershell args : {0}" -f ($args -join ", ") | write-host #"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- --- # -- --- --- ---
#<powershell-payload-pre-tcl> #<powershell-pre-launch-subprocess>
#</powershell-payload-pre-tcl> #</powershell-pre-launch-subprocess>
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<powershell-launch-tcl> #<powershell-launch-subprocess>
tclsh $scriptname $args tclsh $scriptname $args
#</powershell-launch-tcl> #"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<powershell-payload-post-tcl> #<powershell-post-launch-subprocess>
#</powershell-payload-post-tcl> #</powershell-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload # -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
Exit $LASTEXITCODE Exit $LASTEXITCODE
# heredoc2 for powershell to ignore block below # heredoc2 for powershell to ignore block below
$1 = @' $1 = @'
@ -498,7 +715,7 @@ $1 = @'
: \ : \
@REM @ECHO exitcode: !task_exitcode! @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! @EXIT /B !task_exitcode!
# cmd has exited # cmd has exited
@ -509,6 +726,7 @@ $1 = @'
# -- powershell multiline comment # -- powershell multiline comment
#> #>
<# <#
no script engine should try to run me
# id:tailblock1 # id:tailblock1
# <ctrl-z> # <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^ : "[rename set s;proc Hide x {proc $x args {}};Hide :]" "\$(function : {<#pwsh#>})" ^
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @' 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 + ' \ : 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 + \ : .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" + : "[Hide @ECHO; Hide ); Hide (;Hide echo; Hide @REM]#not necessary but can help avoid errs in testing"
: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH' : << '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 \ : 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 ############################################################################################################################
@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, 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 It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################ @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 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 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 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 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 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 @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[10]=pwsh"
@SET "shells[11]=sh" @SET "shells[11]=sh"
@set "shells[12]=bash" @set "shells[12]=bash"
@SET "shells[13]=tclsh" @SET "shells[13]=tclsh"
@SET "shells[14]=perl"
: <nextshell> : <nextshell>
@SET "nextshell=13" @SET "nextshell=13"
: </nextshell> : </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 -- 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 -- Even something as simple as adding or removing an @REM
@REM -- From within punkshell - use: @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 -- to check your templates or final wrapped scripts for byte boundary issues
@REM -- It will report any labels that are on boundaries @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 -- 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 -- 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 -- 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 -- '@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 -- 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 -- 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 ############################################################################################################################
@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections @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 "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs"
@SET arglist=%* @SET arglist=%*
@SET "qstrippedargs=args%arglist%" @IF "%1"=="PUNK-ELEVATED" (
@SET "qstrippedargs=%qstrippedargs:"=%"
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
GOTO :gotPrivileges GOTO :gotPrivileges
) )
@IF !asadmin!==1 ( @IF !asadmin!==1 (
net file 1>NUL 2>NUL net file 1>NUL 2>NUL
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges ) @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 @GOTO skip_privileges
:getPrivileges :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 Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%"
@ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%" @ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%"
@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%" @ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%" @ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%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 @ECHO Launching script in new windows due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %* @"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B @EXIT /B
@ -136,7 +113,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@REM setlocal & pushd . @REM setlocal & pushd .
@PUSHD . @PUSHD .
@cd /d %~dp0 @cd /d %~dp0
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" ( @IF "%1"=="PUNK-ELEVATED" (
@DEL "%vbsGetPrivileges%" 1>nul 2>nul @DEL "%vbsGetPrivileges%" 1>nul 2>nul
@SET arglist=%arglist:~14% @SET arglist=%arglist:~14%
) )
@ -147,7 +124,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@if not exist "%~dp0%~n0.ps1" ( @if not exist "%~dp0%~n0.ps1" (
@SET need_ps1=1 @SET need_ps1=1
) ELSE ( ) 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" @REM @ECHO "files same"
@SET need_ps1=0 @SET need_ps1=0
) )
@ -157,7 +134,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@SET need_ps1=1 @SET need_ps1=1
:pscontinue :pscontinue
@IF !need_ps1!==1 ( @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 /? @REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "!shells[%nextshell%]!"=="pwsh" ( @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 ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 ( IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% & SET task_exitcode=!errorlevel!
SET task_exitcode=!errorlevel!
) ELSE ( ) ELSE (
REM CALL powershell -nop -nol -c write-host powershell-found REM CALL powershell -nop -nol -c write-host powershell-found
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %* 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" ( IF "!shells[%nextshell%]!"=="bash" (
CALL :getWslPath %winpath% wslpath CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%" REM ECHO wslfullpath "!wslpath!%fname%"
!shells[%nextshell%]! "!wslpath!%fname%" %arglist% !shells[%nextshell%]! "!wslpath!%fname%" %arglist% & SET task_exitcode=!errorlevel!
SET task_exitcode=!errorlevel!
) ELSE ( ) ELSE (
REM probably tclsh or sh REM probably tclsh or sh
IF NOT "x%keyRemoved%"=="x%validshells%" ( 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 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 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% !shells[%nextshell%]! "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel!
SET task_exitcode=!errorlevel!
) ELSE ( ) ELSE (
ECHO %fname% has invalid nextshell value ^(%nextshell%^) !shells[%nextshell%]! valid options are %validshells% ECHO %fname% has invalid nextshell value ^(%nextshell%^) !shells[%nextshell%]! valid options are %validshells%
SET task_exitcode=66 SET task_exitcode=66
@REM boundary padding
@REM boundary padding
GOTO :exit_multishell GOTO :exit_multishell
) )
) )
) )
@REM batch file library functions @REM batch file library functions
@REM boundary padding
@GOTO :endlib @GOTO :endlib
:getWslPath :getWslPath
@ -256,7 +227,6 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
) )
@EXIT /B @EXIT /B
@REM boundary padding @REM boundary padding
@REM boundary padding
:getNormalizedScriptTail :getNormalizedScriptTail
@SETLOCAL @SETLOCAL
@SET "result=%~nx0" @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 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
@REM boundary padding
@REM boundary padding
@SETLOCAL @SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash @CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash @CALL :stringContains %~1 "/" hasForwardSlash
@ -412,15 +380,9 @@ namespace eval ::punk::multishell {
# -- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-pre-launch-subprocess> #<tcl-payload>
#</tcl-pre-launch-subprocess> #</tcl-payload>
#<tcl-launch-subprocess>
#</tcl-launch-subproces>
#<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" # -- 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 # -- 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. # -- if sh/bash scripting needs to run on windows too.
# -- # --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### # ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload # -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
exitcode=0
#printf "start of bash or sh code" #printf "start of bash or sh code"
#<shell-pre-launch-subprocess> #<shell-payload-pre-tcl>
#</shell-pre-launch-subprocess> #</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 #-- sh/bash launches Tcl here instead of shebang line at top
#-- use exec to use exitcode (if any) directly from the tcl script #-- use exec to use exitcode (if any) directly from the tcl script
#exec /usr/bin/env tclsh "$0" "$@" #exec /usr/bin/env tclsh "$0" "$@"
#-- alternative - can run sh/bash script after the tcl call. #-- alternative - can run sh/bash script after the tcl call.
/usr/bin/env tclsh "$0" "$@" /usr/bin/env tclsh "$0" "$@"
exitcode=$? exitcode=$?
#echo "sh/bash reporting tcl exitcode: ${exitcode}" #echo "tcl exitcode: ${exitcode}"
#-- override exitcode example #-- override exitcode example
#exit 66 #exit 66
#</shell-launch-subprocess> #</shell-launch-tcl>
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<shell-post-launch-subprocess> #<shell-payload-post-tcl>
#</shell-post-launch-subproces> #</shell-payload-post-tcl>
#printf "sh/bash done \n" #printf "sh/bash done \n"
@ -486,57 +448,7 @@ exitcode=$?
#------------------------------------------------------ #------------------------------------------------------
fi fi
exit ${exitcode} 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 } # 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 # 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 # -- Do not edit if current file is the .ps1
# -- Edit the corresponding .cmd and it will autocopy # -- 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 # -- 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 } 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 # -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host #"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host #"Script Name : {0}" -f $scriptname | write-host
@ -625,22 +470,22 @@ function GetDynamicParamDictionary {
#"powershell args : {0}" -f ($args -join ", ") | write-host #"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- --- # -- --- --- ---
#<powershell-pre-launch-subprocess> #<powershell-payload-pre-tcl>
#</powershell-pre-launch-subprocess> #</powershell-payload-pre-tcl>
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<powershell-launch-subprocess> #<powershell-launch-tcl>
tclsh $scriptname $args tclsh $scriptname $args
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host #</powershell-launch-tcl>
#</powershell-launch-subprocess>
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<powershell-post-launch-subprocess> #<powershell-payload-post-tcl>
#</powershell-post-launch-subprocess> #</powershell-payload-post-tcl>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload # -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
Exit $LASTEXITCODE Exit $LASTEXITCODE
# heredoc2 for powershell to ignore block below # heredoc2 for powershell to ignore block below
$1 = @' $1 = @'
@ -653,7 +498,7 @@ $1 = @'
: \ : \
@REM @ECHO exitcode: !task_exitcode! @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! @EXIT /B !task_exitcode!
# cmd has exited # cmd has exited
@ -664,7 +509,6 @@ $1 = @'
# -- powershell multiline comment # -- powershell multiline comment
#> #>
<# <#
no script engine should try to run me
# id:tailblock1 # id:tailblock1
# <ctrl-z> # <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]" dict set map %marker% "[punk::ansi::a bold cyan]MERGEDBLOCK[punk::ansi::a]"
puts $outc [string map $map $opt_blocksep] puts $outc [string map $map $opt_blocksep]
} }
set blockresult ""
if {$opt_shrink_textfree_blocks} { if {$opt_shrink_textfree_blocks} {
set teststripped [punk::ansi::ansistrip $mergedblock] set teststripped [punk::ansi::ansistrip $mergedblock]
if {[string trim $teststripped] ne ""} { if {[string trim $teststripped] ne ""} {

2
src/modules/punk/zip-999999.0a1.0.tm

@ -400,7 +400,7 @@ tcl::namespace::eval punk::zip {
#todo - if it was internal preamble - need to adjust offsets to fix the split off zipfile #todo - if it was internal preamble - need to adjust offsets to fix the split off zipfile
set zout [open $outfile_zip w] set zout [open $outfile_zip w]
fconfigure $zout -encoding iso8859-1 -translation binary fconfigure $zout -encoding iso8859-1 -translation binary
chan copy $inzip $zout chan copy $inzip $zout
close $zout close $zout
} }
close $inzip close $inzip

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

@ -2839,56 +2839,77 @@ 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. #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]" puts stdout "building $vfsname.new with zipfs vfsdir:$vfstail cwd: [pwd]"
file mkdir $targetvfs file mkdir $targetvfs
set rtmountpoint //zipfs:/rtmounts/$runtime_fullname set raw_runtime $buildfolder/raw_$runtime_fullname
if {![file exists $rtmountpoint]} { if {[info commands ::tcl::zipfs::mount] ne ""} {
if {[catch {
tcl::zipfs::mount $building_runtime rtmounts/$runtime_fullname set rtmountpoint //zipfs:/rtmounts/$runtime_fullname
} errM]} {
puts stderr "Failed to mount $building_runtime using standard api. Err:$errM\n trying reverse args on tcl::zipfs::mount..." if {![file exists $rtmountpoint]} {
if {[catch { if {[catch {
tcl::zipfs::mount rtmounts/$runtime_fullname $building_runtime tcl::zipfs::mount $building_runtime rtmounts/$runtime_fullname
} errM]} { } errM]} {
puts stderr "ALSO Failed to mount $building_runtime using reverse args to api. Err:$errM - no mountable zipfs on runtime?" puts stderr "Failed to mount $building_runtime using standard api. Err:$errM\n trying reverse args on tcl::zipfs::mount..."
if {[catch {
tcl::zipfs::mount rtmounts/$runtime_fullname $building_runtime
} errM]} {
puts stderr "ALSO Failed to mount $building_runtime using reverse args to api. Err:$errM - no mountable zipfs on runtime?"
}
} }
} }
}
#strip any existing zipfs on the runtime.. #strip any existing zipfs on the runtime..
#2024 - 'zipfs info //zipfs:/mountpoint' is supposed to give us the offset - but it doesn't if the exe has been 'adjusted' to use file offsets. #2024 - 'zipfs info //zipfs:/mountpoint' is supposed to give us the offset - but it doesn't if the exe has been 'adjusted' to use file offsets.
#which unfortunately Tcl does by default after the 2021 'fix' :( #which unfortunately Tcl does by default after the 2021 'fix' :(
#https://core.tcl-lang.org/tcl/tktview/aaa84fbbc5 #https://core.tcl-lang.org/tcl/tktview/aaa84fbbc5
set raw_runtime $buildfolder/raw_$runtime_fullname if {[file exists $rtmountpoint]} {
if {[file exists $rtmountpoint]} { merge_over $rtmountpoint $targetvfs
merge_over $rtmountpoint $targetvfs #see if we can extract the exe part
#see if we can extract the exe part set baseoffset [lindex [tcl::zipfs::info $rtmountpoint] 3]
set baseoffset [lindex [tcl::zipfs::info $rtmountpoint] 3] if {$baseoffset != 0} {
if {$baseoffset != 0} { #tcl was able to determine the compressed-data offset
#tcl was able to determine the compressed-data offset #either because runtime is a basic catted exe+zip, or Tcl fixed 'zipfs info'
#either because runtime is a basic catted exe+zip, or Tcl fixed 'zipfs info' set fdrt [open $building_runtime r]
set fdrt [open $building_runtime r] chan configure $fdrt -translation binary
chan configure $fdrt -translation binary set exedata [read $fdrt $baseoffset] ;#may include stored password and ending header // REVIEW - strip it?
set exedata [read $fdrt $baseoffset] ;#may include stored password and ending header // REVIEW - strip it? close $fdrt
close $fdrt set fdraw [open $raw_runtime w]
set fdraw [open $raw_runtime w] chan configure $fdraw -translation binary
chan configure $fdraw -translation binary puts -nonewline $fdraw $exedata
puts -nonewline $fdraw $exedata close $fdraw
close $fdraw } else {
#presumably the supplied building_runtime has had its offsets adjusted so that it all appears within offsets off the zip. (file relative offsets)
#due to zipfs info bug - zipfs now can't tell us the offset of the compressed data.
#we need to use a similarly assumptive method as tclZipfs.c uses to determine the start of the compressed contents
package require punk::zip
#we don't technically need to extract the raw exe for 'zip' - as zipfs mkimg can work on the combined file (ignores zip)
# - but for consistency we want raw_runtime to be emitted in the filesystem.
punk::zip::extract_preamble $building_runtime $raw_runtime
}
} else { } else {
#presumably the supplied building_runtime has had its offsets adjusted so that it all appears within offsets off the zip. (file relative offsets) #the input building_runtime wasn't mountable as a zip - so presumably a plain executable
#due to zipfs info bug - zipfs now can't tell us the offset of the compressed data. #runtime executable possibly with kit/cookfs etc attached?
#we need to use a similarly assumptive method as tclZipfs.c uses to determine the start of the compressed contents #If not - init.tcl probably won't be found? should we even proceed ??
package require punk::zip 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"
#we don't technically need to extract the raw exe for 'zip' - as zipfs mkimg can work on the combined file (ignores zip) file copy -force $building_runtime $raw_runtime
# - but for consistency we want raw_runtime to be emitted in the filesystem.
punk::zip::extract_preamble $building_runtime $raw_runtime
} }
} else { } else {
#the input building_runtime wasn't mountable - so presumably a plain executable package require punk::zip
#set building_runtime $buildfolder/build_$runtime_fullname ;#working copy of runtime executable - (possibly with kit/zipfs/cookfs etc attached!) #tcl we are calling with doesn't have zipfs - can't mount
#set raw_runtime $buildfolder/raw_$runtime_fullname puts stderr "WARNING: tcl shell '[info nameofexecutable]' being used to build doesn't have zipfs - falling back to punk::zip::extract_preamble"
file copy -force $building_runtime $raw_runtime 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 merge_over $sourcefolder/vfs/_vfscommon.vfs $targetvfs

315
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
package require punk::mix::base package require punk::mix::base
package require punk::fileline package require punk::fileline
package require punk::ansi
#*** !doctools #*** !doctools
@ -78,6 +79,7 @@ namespace eval punk::mix::commandset::scriptwrap {
#[list_begin definitions] #[list_begin definitions]
namespace export {[a-z]*} namespace export {[a-z]*}
namespace import ::punk::ansi::a ::punk::ansi::a+
namespace eval fileline { namespace eval fileline {
namespace import ::punk::fileline::lib::* namespace import ::punk::fileline::lib::*
@ -807,7 +809,7 @@ namespace eval punk::mix::commandset::scriptwrap {
} }
return $result 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 #scriptset name to substitute multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
#set usage "" #set usage ""
#append usage "Use directly with the script file to wrap, or supply the name of a scriptset" \n #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}]} { if {[tomlish::dict::path::exists $tomldict {.application.template}]} {
dict set resultd template [tomlish::dict::path::get $tomldict {.application.template.value}] 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] set scripts [list]
if {[tomlish::dict::path::exists $tomldict {.application.scripts.value}]} { if {[tomlish::dict::path::exists $tomldict {.application.scripts.value}]} {
set arrvalues [tomlish::dict::path::get $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 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 { punk::args::define {
@id -id ::punk::mix::commandset::scriptwrap::multishell @id -id ::punk::mix::commandset::scriptwrap::multishell
@cmd -name 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 -returnextra -type boolean -default 0
@values -minvalues 0 -maxvalues 0 @values -minvalues 0 -maxvalues 0
} }
#: <nextshell> #: <<nextshell_start>>
#@SET "nextshellpath[win32___________]=tclsh___________________________" #@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________" #@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________" #@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________" #@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________" #@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________" #@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________" #@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________" #@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________" #@SET "nextshelltype[other___________]=tcl_____________"
#: </nextshell> #: <<nextshell_end>>
proc multishell {args} { proc multishell {args} {
set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell] set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell]
lassign [dict values $argd] leaders opts values received lassign [dict values $argd] leaders opts values received
@ -947,7 +1014,7 @@ namespace eval punk::mix::commandset::scriptwrap {
set startdir [pwd] set startdir [pwd]
set allowed_extensions [list tcl ps1 sh bash pl] set allowed_extensions [list tcl ps1 sh bash pl]
#TODO - distinct sections for sh vs bash? needs experiments.. #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 sh bash bash pl perl]
set extension_langs [list tcl tcl ps1 powershell sh shell bash shell 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 list_input_files [list]
set has_config 0
set configd [dict create] set configd [dict create]
if {$scriptset ne ""} { if {$scriptset ne ""} {
puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'" puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'"
#.toml file may or may not exist #.toml file may or may not exist
if {[file exists ${scriptset}_wrap.toml]} { 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 configd [_read_scriptset_wrap_tomlfile $scriptdir/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} { if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts] set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts { foreach s $configured_scripts {
@ -1008,6 +1077,7 @@ namespace eval punk::mix::commandset::scriptwrap {
return false return false
} }
} else { } else {
set configd [_default_configd]
puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml" puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml"
puts stdout "Will look for the following scripts in $scriptdir" puts stdout "Will look for the following scripts in $scriptdir"
foreach e $allowed_extensions { foreach e $allowed_extensions {
@ -1018,12 +1088,17 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptdir/$scriptset.$e lappend list_input_files $scriptdir/$scriptset.$e
} }
} }
dict set configd scripts $list_input_files
} }
} else { } else {
set configd [_default_configd]
#expect a single script #expect a single script
if {[file exists $specified_path]} { if {[file exists $specified_path]} {
lappend list_input_files $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}] set found_script [expr {[llength $list_input_files] > 0}]
@ -1061,8 +1136,9 @@ namespace eval punk::mix::commandset::scriptwrap {
if {$scriptset ne ""} { if {$scriptset ne ""} {
#.toml file may or may not exist #.toml file may or may not exist
if {[file exists $scriptroot/${scriptset}_wrap.toml]} { 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 configd [_read_scriptset_wrap_tomlfile $scriptroot/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} { if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts] set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts { foreach s $configured_scripts {
@ -1084,6 +1160,8 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptroot/$scriptset.$e lappend list_input_files $scriptroot/$scriptset.$e
} }
} }
set configd [_default_configd]
dict set configd scripts [lmap f $list_input_files {file tail $f}]
} }
} else { } else {
#expect a single script #expect a single script
@ -1094,6 +1172,8 @@ namespace eval punk::mix::commandset::scriptwrap {
} }
lappend list_input_files $scriptroot/$filepath_or_scriptset 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}] 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 #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] set templatename [dict get $configd template]
} else { } else {
if {$opt_template eq "\uFFFF"} { if {$opt_template eq "\uFFFF"} {
@ -1119,6 +1199,7 @@ namespace eval punk::mix::commandset::scriptwrap {
} else { } else {
set templatename $opt_template set templatename $opt_template
} }
dict set configd template $templatename
} }
set templatename_root [file rootname [file tail $templatename]] set templatename_root [file rootname [file tail $templatename]]
@ -1199,21 +1280,26 @@ namespace eval punk::mix::commandset::scriptwrap {
#todo #todo
#output_file extension may also depend on the template being used.. and/or the <scriptset>_wrap.toml config #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
package require platform set thisplatform [string tolower [platform::identify]]
set thisplatform [string tolower [platform::identify]] set ptype [lindex [split $thisplatform -] 0]
set ptype [lindex [split $thisplatform -] 0] switch -- $ptype {
switch -- $ptype { win32 - dragonflybsd - freebsd - netbsd - linux - macosx {}
win32 - dragonflybsd - freebsd - netbsd - linux - macosx {} default {
default { set ptype other
set ptype other
}
} }
}
if {$has_config} {
set out [dict get $configd $ptype outputfile] set out [dict get $configd $ptype outputfile]
set output_file [file join $output_folder $out] 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 { } else {
#no _wrap.toml file available #no _wrap.toml file available
if {$::tcl_platform(platform) eq "windows"} { if {$ptype eq "win32"} {
set output_extension .cmd set output_extension .cmd
} else { } else {
set output_extension .sh set output_extension .sh
@ -1224,6 +1310,11 @@ namespace eval punk::mix::commandset::scriptwrap {
set infile [lindex $list_input_files 0] set infile [lindex $list_input_files 0]
set output_file [file join $output_folder [file rootname [file tail $infile]]$output_extension] 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]" 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_force} {
if {$opt_askme} { 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"} { 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." 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 $objFile_existing destroy
@ -1264,92 +1355,122 @@ namespace eval punk::mix::commandset::scriptwrap {
puts stdout $ln puts stdout $ln
} }
puts stdout "-----------------------------------------------\n" 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 lang_data [dict create]
set filepath [lindex $list_input_files 0] foreach filepath $list_input_files {
set fdscript [open $filepath r] set script_ext [string trim [file extension $filepath] .]
fconfigure $fdscript -translation binary set lang [dict get $extension_langs [string tolower $script_ext]]
set script_data [read $fdscript] set fdscript [open $filepath r]
close $fdscript fconfigure $fdscript -translation binary
puts stdout "Read [string length $script_data] bytes of template data.." set script_data [read $fdscript]
set script_lines [split $script_data \n] close $fdscript
puts stdout "Displaying first 3 lines of your script between dashed lines..." puts stdout "Read [string length $script_data] bytes of template data for lang: $lang"
puts stdout "-----------------------------------------------" set script_lines [split $script_data \n]
foreach ln [lrange $script_lines 0 3] { dict set lang_data $lang $script_lines
puts stdout $ln puts stdout "Displaying first 3 lines of your script [file tail $filepath] between dashed lines..."
} puts stdout "[a green]$filepath[a]"
puts stdout "-----------------------------------------------\n" puts stdout "-----------------------------------------------"
puts stdout "Target for above script data is '$output_file'" foreach ln [lrange $script_lines 0 3] {
set script_ext [string trim [file extension $filepath] .] puts stdout $ln
set lang [dict get $extension_langs [string tolower $script_ext]] }
puts stdout "Language of script being wrapped is $lang" puts stdout "-----------------------------------------------\n"
if {$opt_askme} { puts stdout "Target for script data is '$output_file'"
set answer [util::askuser "Does this look correct? Y|N"] puts stdout "Language of script being wrapped is [a bold yellow]$lang[a]"
if {[string tolower $answer] ne "y"} { if {$opt_askme} {
puts stderr "mix new aborting due to user response '$answer' (required Y or y to proceed) use -askme 0 to avoid prompts." set answer [util::askuser "Does this look correct? Y|N"]
return if {[string tolower $answer] ne "y"} {
puts stderr "mix new aborting due to user response '$answer' (required Y or y to proceed) use -askme 0 to avoid prompts."
return
}
} }
} }
set start_idx 0 set template_ranges [list]
set end_idx 0 set data_items [list]
set line_idx 0 set trange [list 0]
set existing_payload [list] set line_idx -1
set opentag ""
foreach ln $template_lines { foreach ln $template_lines {
incr line_idx
if {[string match "#<$lang-pre-launch-subprocess>*" $ln]} { if {$opentag eq ""} {
set start_idx $line_idx if {[string match ": <<asadmin_start>>*" $ln]} {
} elseif {[string match "#</$lang-pre-launch-subprocess>*" $ln]} { set opentag asadmin
set end_idx $line_idx lset trange 1 $line_idx ;#include tag in template
break lappend template_ranges $trange
} elseif {$start_idx > 0} { set trange [list $line_idx]
if {$end_idx > 0} { set asadmin [dict get $configd as_admin]
lappend existing_payload [string trim $ln] 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 { } else {
switch -- [string range $opentag 0 6] {
asadmin {
if {[string match ": <<asadmin_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
nextshe {
if {[string match ": <<nextshell_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
payload {
set lang [string range $opentag 8 end] ;#payload-xxx
if {[string match "#</$lang-payload>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
}
} }
incr line_idx
} }
if {($start_idx == 0) || ($end_idx == 0)} { if {$opentag eq ""} {
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" lset trange 1 end
lappend template_ranges $trange
} else {
error "multishell - unable to find closing tag for '$opentag'"
} }
set existing_string [join $existing_payload \n] set newscript ""
if {[string length [string trim $existing_string]]} { foreach trange $template_ranges item $data_items {
puts stdout "EXISTING <$lang-pre-launch-subprocess> PAYLOAD!!" append newscript [join [lrange $template_lines {*}$trange] \n]
puts stdout "-----------------------------------------------\n" if {$item ne ""} {
puts stdout $existing_string append newscript \n $item \n
puts stdout "-----------------------------------------------\n" } else {
error "wrap_in_multishell found existing payload for language $lang ... aborting." append newscript \n
#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
}
}
} }
} }
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 "New script is [string length $newscript] bytes"
puts stdout $newscript puts stdout $newscript
set fdtarget [open $output_file w] set fdtarget [open $output_file w]
fconfigure $fdtarget -translation binary fconfigure $fdtarget -translation binary
puts -nonewline $fdtarget $newscript puts -nonewline $fdtarget $newscript

2
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/zip-0.1.1.tm

@ -400,7 +400,7 @@ tcl::namespace::eval punk::zip {
#todo - if it was internal preamble - need to adjust offsets to fix the split off zipfile #todo - if it was internal preamble - need to adjust offsets to fix the split off zipfile
set zout [open $outfile_zip w] set zout [open $outfile_zip w]
fconfigure $zout -encoding iso8859-1 -translation binary fconfigure $zout -encoding iso8859-1 -translation binary
chan copy $inzip $zout chan copy $inzip $zout
close $zout close $zout
} }
close $inzip close $inzip

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

@ -2839,56 +2839,77 @@ 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. #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]" puts stdout "building $vfsname.new with zipfs vfsdir:$vfstail cwd: [pwd]"
file mkdir $targetvfs file mkdir $targetvfs
set rtmountpoint //zipfs:/rtmounts/$runtime_fullname set raw_runtime $buildfolder/raw_$runtime_fullname
if {![file exists $rtmountpoint]} { if {[info commands ::tcl::zipfs::mount] ne ""} {
if {[catch {
tcl::zipfs::mount $building_runtime rtmounts/$runtime_fullname set rtmountpoint //zipfs:/rtmounts/$runtime_fullname
} errM]} {
puts stderr "Failed to mount $building_runtime using standard api. Err:$errM\n trying reverse args on tcl::zipfs::mount..." if {![file exists $rtmountpoint]} {
if {[catch { if {[catch {
tcl::zipfs::mount rtmounts/$runtime_fullname $building_runtime tcl::zipfs::mount $building_runtime rtmounts/$runtime_fullname
} errM]} { } errM]} {
puts stderr "ALSO Failed to mount $building_runtime using reverse args to api. Err:$errM - no mountable zipfs on runtime?" puts stderr "Failed to mount $building_runtime using standard api. Err:$errM\n trying reverse args on tcl::zipfs::mount..."
if {[catch {
tcl::zipfs::mount rtmounts/$runtime_fullname $building_runtime
} errM]} {
puts stderr "ALSO Failed to mount $building_runtime using reverse args to api. Err:$errM - no mountable zipfs on runtime?"
}
} }
} }
}
#strip any existing zipfs on the runtime.. #strip any existing zipfs on the runtime..
#2024 - 'zipfs info //zipfs:/mountpoint' is supposed to give us the offset - but it doesn't if the exe has been 'adjusted' to use file offsets. #2024 - 'zipfs info //zipfs:/mountpoint' is supposed to give us the offset - but it doesn't if the exe has been 'adjusted' to use file offsets.
#which unfortunately Tcl does by default after the 2021 'fix' :( #which unfortunately Tcl does by default after the 2021 'fix' :(
#https://core.tcl-lang.org/tcl/tktview/aaa84fbbc5 #https://core.tcl-lang.org/tcl/tktview/aaa84fbbc5
set raw_runtime $buildfolder/raw_$runtime_fullname if {[file exists $rtmountpoint]} {
if {[file exists $rtmountpoint]} { merge_over $rtmountpoint $targetvfs
merge_over $rtmountpoint $targetvfs #see if we can extract the exe part
#see if we can extract the exe part set baseoffset [lindex [tcl::zipfs::info $rtmountpoint] 3]
set baseoffset [lindex [tcl::zipfs::info $rtmountpoint] 3] if {$baseoffset != 0} {
if {$baseoffset != 0} { #tcl was able to determine the compressed-data offset
#tcl was able to determine the compressed-data offset #either because runtime is a basic catted exe+zip, or Tcl fixed 'zipfs info'
#either because runtime is a basic catted exe+zip, or Tcl fixed 'zipfs info' set fdrt [open $building_runtime r]
set fdrt [open $building_runtime r] chan configure $fdrt -translation binary
chan configure $fdrt -translation binary set exedata [read $fdrt $baseoffset] ;#may include stored password and ending header // REVIEW - strip it?
set exedata [read $fdrt $baseoffset] ;#may include stored password and ending header // REVIEW - strip it? close $fdrt
close $fdrt set fdraw [open $raw_runtime w]
set fdraw [open $raw_runtime w] chan configure $fdraw -translation binary
chan configure $fdraw -translation binary puts -nonewline $fdraw $exedata
puts -nonewline $fdraw $exedata close $fdraw
close $fdraw } else {
#presumably the supplied building_runtime has had its offsets adjusted so that it all appears within offsets off the zip. (file relative offsets)
#due to zipfs info bug - zipfs now can't tell us the offset of the compressed data.
#we need to use a similarly assumptive method as tclZipfs.c uses to determine the start of the compressed contents
package require punk::zip
#we don't technically need to extract the raw exe for 'zip' - as zipfs mkimg can work on the combined file (ignores zip)
# - but for consistency we want raw_runtime to be emitted in the filesystem.
punk::zip::extract_preamble $building_runtime $raw_runtime
}
} else { } else {
#presumably the supplied building_runtime has had its offsets adjusted so that it all appears within offsets off the zip. (file relative offsets) #the input building_runtime wasn't mountable as a zip - so presumably a plain executable
#due to zipfs info bug - zipfs now can't tell us the offset of the compressed data. #runtime executable possibly with kit/cookfs etc attached?
#we need to use a similarly assumptive method as tclZipfs.c uses to determine the start of the compressed contents #If not - init.tcl probably won't be found? should we even proceed ??
package require punk::zip 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"
#we don't technically need to extract the raw exe for 'zip' - as zipfs mkimg can work on the combined file (ignores zip) file copy -force $building_runtime $raw_runtime
# - but for consistency we want raw_runtime to be emitted in the filesystem.
punk::zip::extract_preamble $building_runtime $raw_runtime
} }
} else { } else {
#the input building_runtime wasn't mountable - so presumably a plain executable package require punk::zip
#set building_runtime $buildfolder/build_$runtime_fullname ;#working copy of runtime executable - (possibly with kit/zipfs/cookfs etc attached!) #tcl we are calling with doesn't have zipfs - can't mount
#set raw_runtime $buildfolder/raw_$runtime_fullname puts stderr "WARNING: tcl shell '[info nameofexecutable]' being used to build doesn't have zipfs - falling back to punk::zip::extract_preamble"
file copy -force $building_runtime $raw_runtime 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 merge_over $sourcefolder/vfs/_vfscommon.vfs $targetvfs

315
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
package require punk::mix::base package require punk::mix::base
package require punk::fileline package require punk::fileline
package require punk::ansi
#*** !doctools #*** !doctools
@ -78,6 +79,7 @@ namespace eval punk::mix::commandset::scriptwrap {
#[list_begin definitions] #[list_begin definitions]
namespace export {[a-z]*} namespace export {[a-z]*}
namespace import ::punk::ansi::a ::punk::ansi::a+
namespace eval fileline { namespace eval fileline {
namespace import ::punk::fileline::lib::* namespace import ::punk::fileline::lib::*
@ -807,7 +809,7 @@ namespace eval punk::mix::commandset::scriptwrap {
} }
return $result 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 #scriptset name to substitute multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
#set usage "" #set usage ""
#append usage "Use directly with the script file to wrap, or supply the name of a scriptset" \n #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}]} { if {[tomlish::dict::path::exists $tomldict {.application.template}]} {
dict set resultd template [tomlish::dict::path::get $tomldict {.application.template.value}] 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] set scripts [list]
if {[tomlish::dict::path::exists $tomldict {.application.scripts.value}]} { if {[tomlish::dict::path::exists $tomldict {.application.scripts.value}]} {
set arrvalues [tomlish::dict::path::get $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 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 { punk::args::define {
@id -id ::punk::mix::commandset::scriptwrap::multishell @id -id ::punk::mix::commandset::scriptwrap::multishell
@cmd -name 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 -returnextra -type boolean -default 0
@values -minvalues 0 -maxvalues 0 @values -minvalues 0 -maxvalues 0
} }
#: <nextshell> #: <<nextshell_start>>
#@SET "nextshellpath[win32___________]=tclsh___________________________" #@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________" #@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________" #@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________" #@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________" #@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________" #@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________" #@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________" #@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________" #@SET "nextshelltype[other___________]=tcl_____________"
#: </nextshell> #: <<nextshell_end>>
proc multishell {args} { proc multishell {args} {
set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell] set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell]
lassign [dict values $argd] leaders opts values received lassign [dict values $argd] leaders opts values received
@ -947,7 +1014,7 @@ namespace eval punk::mix::commandset::scriptwrap {
set startdir [pwd] set startdir [pwd]
set allowed_extensions [list tcl ps1 sh bash pl] set allowed_extensions [list tcl ps1 sh bash pl]
#TODO - distinct sections for sh vs bash? needs experiments.. #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 sh bash bash pl perl]
set extension_langs [list tcl tcl ps1 powershell sh shell bash shell 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 list_input_files [list]
set has_config 0
set configd [dict create] set configd [dict create]
if {$scriptset ne ""} { if {$scriptset ne ""} {
puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'" puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'"
#.toml file may or may not exist #.toml file may or may not exist
if {[file exists ${scriptset}_wrap.toml]} { 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 configd [_read_scriptset_wrap_tomlfile $scriptdir/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} { if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts] set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts { foreach s $configured_scripts {
@ -1008,6 +1077,7 @@ namespace eval punk::mix::commandset::scriptwrap {
return false return false
} }
} else { } else {
set configd [_default_configd]
puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml" puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml"
puts stdout "Will look for the following scripts in $scriptdir" puts stdout "Will look for the following scripts in $scriptdir"
foreach e $allowed_extensions { foreach e $allowed_extensions {
@ -1018,12 +1088,17 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptdir/$scriptset.$e lappend list_input_files $scriptdir/$scriptset.$e
} }
} }
dict set configd scripts $list_input_files
} }
} else { } else {
set configd [_default_configd]
#expect a single script #expect a single script
if {[file exists $specified_path]} { if {[file exists $specified_path]} {
lappend list_input_files $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}] set found_script [expr {[llength $list_input_files] > 0}]
@ -1061,8 +1136,9 @@ namespace eval punk::mix::commandset::scriptwrap {
if {$scriptset ne ""} { if {$scriptset ne ""} {
#.toml file may or may not exist #.toml file may or may not exist
if {[file exists $scriptroot/${scriptset}_wrap.toml]} { 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 configd [_read_scriptset_wrap_tomlfile $scriptroot/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} { if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts] set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts { foreach s $configured_scripts {
@ -1084,6 +1160,8 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptroot/$scriptset.$e lappend list_input_files $scriptroot/$scriptset.$e
} }
} }
set configd [_default_configd]
dict set configd scripts [lmap f $list_input_files {file tail $f}]
} }
} else { } else {
#expect a single script #expect a single script
@ -1094,6 +1172,8 @@ namespace eval punk::mix::commandset::scriptwrap {
} }
lappend list_input_files $scriptroot/$filepath_or_scriptset 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}] 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 #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] set templatename [dict get $configd template]
} else { } else {
if {$opt_template eq "\uFFFF"} { if {$opt_template eq "\uFFFF"} {
@ -1119,6 +1199,7 @@ namespace eval punk::mix::commandset::scriptwrap {
} else { } else {
set templatename $opt_template set templatename $opt_template
} }
dict set configd template $templatename
} }
set templatename_root [file rootname [file tail $templatename]] set templatename_root [file rootname [file tail $templatename]]
@ -1199,21 +1280,26 @@ namespace eval punk::mix::commandset::scriptwrap {
#todo #todo
#output_file extension may also depend on the template being used.. and/or the <scriptset>_wrap.toml config #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
package require platform set thisplatform [string tolower [platform::identify]]
set thisplatform [string tolower [platform::identify]] set ptype [lindex [split $thisplatform -] 0]
set ptype [lindex [split $thisplatform -] 0] switch -- $ptype {
switch -- $ptype { win32 - dragonflybsd - freebsd - netbsd - linux - macosx {}
win32 - dragonflybsd - freebsd - netbsd - linux - macosx {} default {
default { set ptype other
set ptype other
}
} }
}
if {$has_config} {
set out [dict get $configd $ptype outputfile] set out [dict get $configd $ptype outputfile]
set output_file [file join $output_folder $out] 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 { } else {
#no _wrap.toml file available #no _wrap.toml file available
if {$::tcl_platform(platform) eq "windows"} { if {$ptype eq "win32"} {
set output_extension .cmd set output_extension .cmd
} else { } else {
set output_extension .sh set output_extension .sh
@ -1224,6 +1310,11 @@ namespace eval punk::mix::commandset::scriptwrap {
set infile [lindex $list_input_files 0] set infile [lindex $list_input_files 0]
set output_file [file join $output_folder [file rootname [file tail $infile]]$output_extension] 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]" 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_force} {
if {$opt_askme} { 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"} { 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." 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 $objFile_existing destroy
@ -1264,92 +1355,122 @@ namespace eval punk::mix::commandset::scriptwrap {
puts stdout $ln puts stdout $ln
} }
puts stdout "-----------------------------------------------\n" 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 lang_data [dict create]
set filepath [lindex $list_input_files 0] foreach filepath $list_input_files {
set fdscript [open $filepath r] set script_ext [string trim [file extension $filepath] .]
fconfigure $fdscript -translation binary set lang [dict get $extension_langs [string tolower $script_ext]]
set script_data [read $fdscript] set fdscript [open $filepath r]
close $fdscript fconfigure $fdscript -translation binary
puts stdout "Read [string length $script_data] bytes of template data.." set script_data [read $fdscript]
set script_lines [split $script_data \n] close $fdscript
puts stdout "Displaying first 3 lines of your script between dashed lines..." puts stdout "Read [string length $script_data] bytes of template data for lang: $lang"
puts stdout "-----------------------------------------------" set script_lines [split $script_data \n]
foreach ln [lrange $script_lines 0 3] { dict set lang_data $lang $script_lines
puts stdout $ln puts stdout "Displaying first 3 lines of your script [file tail $filepath] between dashed lines..."
} puts stdout "[a green]$filepath[a]"
puts stdout "-----------------------------------------------\n" puts stdout "-----------------------------------------------"
puts stdout "Target for above script data is '$output_file'" foreach ln [lrange $script_lines 0 3] {
set script_ext [string trim [file extension $filepath] .] puts stdout $ln
set lang [dict get $extension_langs [string tolower $script_ext]] }
puts stdout "Language of script being wrapped is $lang" puts stdout "-----------------------------------------------\n"
if {$opt_askme} { puts stdout "Target for script data is '$output_file'"
set answer [util::askuser "Does this look correct? Y|N"] puts stdout "Language of script being wrapped is [a bold yellow]$lang[a]"
if {[string tolower $answer] ne "y"} { if {$opt_askme} {
puts stderr "mix new aborting due to user response '$answer' (required Y or y to proceed) use -askme 0 to avoid prompts." set answer [util::askuser "Does this look correct? Y|N"]
return if {[string tolower $answer] ne "y"} {
puts stderr "mix new aborting due to user response '$answer' (required Y or y to proceed) use -askme 0 to avoid prompts."
return
}
} }
} }
set start_idx 0 set template_ranges [list]
set end_idx 0 set data_items [list]
set line_idx 0 set trange [list 0]
set existing_payload [list] set line_idx -1
set opentag ""
foreach ln $template_lines { foreach ln $template_lines {
incr line_idx
if {[string match "#<$lang-pre-launch-subprocess>*" $ln]} { if {$opentag eq ""} {
set start_idx $line_idx if {[string match ": <<asadmin_start>>*" $ln]} {
} elseif {[string match "#</$lang-pre-launch-subprocess>*" $ln]} { set opentag asadmin
set end_idx $line_idx lset trange 1 $line_idx ;#include tag in template
break lappend template_ranges $trange
} elseif {$start_idx > 0} { set trange [list $line_idx]
if {$end_idx > 0} { set asadmin [dict get $configd as_admin]
lappend existing_payload [string trim $ln] 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 { } else {
switch -- [string range $opentag 0 6] {
asadmin {
if {[string match ": <<asadmin_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
nextshe {
if {[string match ": <<nextshell_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
payload {
set lang [string range $opentag 8 end] ;#payload-xxx
if {[string match "#</$lang-payload>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
}
} }
incr line_idx
} }
if {($start_idx == 0) || ($end_idx == 0)} { if {$opentag eq ""} {
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" lset trange 1 end
lappend template_ranges $trange
} else {
error "multishell - unable to find closing tag for '$opentag'"
} }
set existing_string [join $existing_payload \n] set newscript ""
if {[string length [string trim $existing_string]]} { foreach trange $template_ranges item $data_items {
puts stdout "EXISTING <$lang-pre-launch-subprocess> PAYLOAD!!" append newscript [join [lrange $template_lines {*}$trange] \n]
puts stdout "-----------------------------------------------\n" if {$item ne ""} {
puts stdout $existing_string append newscript \n $item \n
puts stdout "-----------------------------------------------\n" } else {
error "wrap_in_multishell found existing payload for language $lang ... aborting." append newscript \n
#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
}
}
} }
} }
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 "New script is [string length $newscript] bytes"
puts stdout $newscript puts stdout $newscript
set fdtarget [open $output_file w] set fdtarget [open $output_file w]
fconfigure $fdtarget -translation binary fconfigure $fdtarget -translation binary
puts -nonewline $fdtarget $newscript puts -nonewline $fdtarget $newscript

2
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/zip-0.1.1.tm

@ -400,7 +400,7 @@ tcl::namespace::eval punk::zip {
#todo - if it was internal preamble - need to adjust offsets to fix the split off zipfile #todo - if it was internal preamble - need to adjust offsets to fix the split off zipfile
set zout [open $outfile_zip w] set zout [open $outfile_zip w]
fconfigure $zout -encoding iso8859-1 -translation binary fconfigure $zout -encoding iso8859-1 -translation binary
chan copy $inzip $zout chan copy $inzip $zout
close $zout close $zout
} }
close $inzip close $inzip

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

@ -2839,56 +2839,77 @@ 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. #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]" puts stdout "building $vfsname.new with zipfs vfsdir:$vfstail cwd: [pwd]"
file mkdir $targetvfs file mkdir $targetvfs
set rtmountpoint //zipfs:/rtmounts/$runtime_fullname set raw_runtime $buildfolder/raw_$runtime_fullname
if {![file exists $rtmountpoint]} { if {[info commands ::tcl::zipfs::mount] ne ""} {
if {[catch {
tcl::zipfs::mount $building_runtime rtmounts/$runtime_fullname set rtmountpoint //zipfs:/rtmounts/$runtime_fullname
} errM]} {
puts stderr "Failed to mount $building_runtime using standard api. Err:$errM\n trying reverse args on tcl::zipfs::mount..." if {![file exists $rtmountpoint]} {
if {[catch { if {[catch {
tcl::zipfs::mount rtmounts/$runtime_fullname $building_runtime tcl::zipfs::mount $building_runtime rtmounts/$runtime_fullname
} errM]} { } errM]} {
puts stderr "ALSO Failed to mount $building_runtime using reverse args to api. Err:$errM - no mountable zipfs on runtime?" puts stderr "Failed to mount $building_runtime using standard api. Err:$errM\n trying reverse args on tcl::zipfs::mount..."
if {[catch {
tcl::zipfs::mount rtmounts/$runtime_fullname $building_runtime
} errM]} {
puts stderr "ALSO Failed to mount $building_runtime using reverse args to api. Err:$errM - no mountable zipfs on runtime?"
}
} }
} }
}
#strip any existing zipfs on the runtime.. #strip any existing zipfs on the runtime..
#2024 - 'zipfs info //zipfs:/mountpoint' is supposed to give us the offset - but it doesn't if the exe has been 'adjusted' to use file offsets. #2024 - 'zipfs info //zipfs:/mountpoint' is supposed to give us the offset - but it doesn't if the exe has been 'adjusted' to use file offsets.
#which unfortunately Tcl does by default after the 2021 'fix' :( #which unfortunately Tcl does by default after the 2021 'fix' :(
#https://core.tcl-lang.org/tcl/tktview/aaa84fbbc5 #https://core.tcl-lang.org/tcl/tktview/aaa84fbbc5
set raw_runtime $buildfolder/raw_$runtime_fullname if {[file exists $rtmountpoint]} {
if {[file exists $rtmountpoint]} { merge_over $rtmountpoint $targetvfs
merge_over $rtmountpoint $targetvfs #see if we can extract the exe part
#see if we can extract the exe part set baseoffset [lindex [tcl::zipfs::info $rtmountpoint] 3]
set baseoffset [lindex [tcl::zipfs::info $rtmountpoint] 3] if {$baseoffset != 0} {
if {$baseoffset != 0} { #tcl was able to determine the compressed-data offset
#tcl was able to determine the compressed-data offset #either because runtime is a basic catted exe+zip, or Tcl fixed 'zipfs info'
#either because runtime is a basic catted exe+zip, or Tcl fixed 'zipfs info' set fdrt [open $building_runtime r]
set fdrt [open $building_runtime r] chan configure $fdrt -translation binary
chan configure $fdrt -translation binary set exedata [read $fdrt $baseoffset] ;#may include stored password and ending header // REVIEW - strip it?
set exedata [read $fdrt $baseoffset] ;#may include stored password and ending header // REVIEW - strip it? close $fdrt
close $fdrt set fdraw [open $raw_runtime w]
set fdraw [open $raw_runtime w] chan configure $fdraw -translation binary
chan configure $fdraw -translation binary puts -nonewline $fdraw $exedata
puts -nonewline $fdraw $exedata close $fdraw
close $fdraw } else {
#presumably the supplied building_runtime has had its offsets adjusted so that it all appears within offsets off the zip. (file relative offsets)
#due to zipfs info bug - zipfs now can't tell us the offset of the compressed data.
#we need to use a similarly assumptive method as tclZipfs.c uses to determine the start of the compressed contents
package require punk::zip
#we don't technically need to extract the raw exe for 'zip' - as zipfs mkimg can work on the combined file (ignores zip)
# - but for consistency we want raw_runtime to be emitted in the filesystem.
punk::zip::extract_preamble $building_runtime $raw_runtime
}
} else { } else {
#presumably the supplied building_runtime has had its offsets adjusted so that it all appears within offsets off the zip. (file relative offsets) #the input building_runtime wasn't mountable as a zip - so presumably a plain executable
#due to zipfs info bug - zipfs now can't tell us the offset of the compressed data. #runtime executable possibly with kit/cookfs etc attached?
#we need to use a similarly assumptive method as tclZipfs.c uses to determine the start of the compressed contents #If not - init.tcl probably won't be found? should we even proceed ??
package require punk::zip 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"
#we don't technically need to extract the raw exe for 'zip' - as zipfs mkimg can work on the combined file (ignores zip) file copy -force $building_runtime $raw_runtime
# - but for consistency we want raw_runtime to be emitted in the filesystem.
punk::zip::extract_preamble $building_runtime $raw_runtime
} }
} else { } else {
#the input building_runtime wasn't mountable - so presumably a plain executable package require punk::zip
#set building_runtime $buildfolder/build_$runtime_fullname ;#working copy of runtime executable - (possibly with kit/zipfs/cookfs etc attached!) #tcl we are calling with doesn't have zipfs - can't mount
#set raw_runtime $buildfolder/raw_$runtime_fullname puts stderr "WARNING: tcl shell '[info nameofexecutable]' being used to build doesn't have zipfs - falling back to punk::zip::extract_preamble"
file copy -force $building_runtime $raw_runtime 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 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] [application]
template="punk.multishell.cmd" template="punk.multishell.cmd"
as_admin=false
#scripts=[ #scripts=[
# "example.sh", # "example.sh",
@ -15,11 +16,16 @@
#valid nextshelltype entries are: tcl perl powershell bash. #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.nextshellpath="tclsh"
win32.nextshelltype="tcl" win32.nextshelltype="tcl"
win32.outputfile="example_out.bat" win32.outputfile="example_out.cmd"
dragonflybsd.nextshellpath="/usr/bin/env tclsh" dragonflybsd.nextshellpath="/usr/bin/env tclsh"
dragonflybsd.nextshelltype="tcl" 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" $url = "https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master/win64/tclsh901t.exe"
$output = "$(join-path $PSScriptRoot "..\runtime\tclkit86bi.exe")" $output = "$(join-path $PSScriptRoot "..\src\runtime\tclsh901t.exe")"
#padding
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 { try {
#Invoke-WebRequest $url -OutFile #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"

315
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
package require punk::mix::base package require punk::mix::base
package require punk::fileline package require punk::fileline
package require punk::ansi
#*** !doctools #*** !doctools
@ -78,6 +79,7 @@ namespace eval punk::mix::commandset::scriptwrap {
#[list_begin definitions] #[list_begin definitions]
namespace export {[a-z]*} namespace export {[a-z]*}
namespace import ::punk::ansi::a ::punk::ansi::a+
namespace eval fileline { namespace eval fileline {
namespace import ::punk::fileline::lib::* namespace import ::punk::fileline::lib::*
@ -807,7 +809,7 @@ namespace eval punk::mix::commandset::scriptwrap {
} }
return $result 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 #scriptset name to substitute multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
#set usage "" #set usage ""
#append usage "Use directly with the script file to wrap, or supply the name of a scriptset" \n #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}]} { if {[tomlish::dict::path::exists $tomldict {.application.template}]} {
dict set resultd template [tomlish::dict::path::get $tomldict {.application.template.value}] 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] set scripts [list]
if {[tomlish::dict::path::exists $tomldict {.application.scripts.value}]} { if {[tomlish::dict::path::exists $tomldict {.application.scripts.value}]} {
set arrvalues [tomlish::dict::path::get $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 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 { punk::args::define {
@id -id ::punk::mix::commandset::scriptwrap::multishell @id -id ::punk::mix::commandset::scriptwrap::multishell
@cmd -name 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 -returnextra -type boolean -default 0
@values -minvalues 0 -maxvalues 0 @values -minvalues 0 -maxvalues 0
} }
#: <nextshell> #: <<nextshell_start>>
#@SET "nextshellpath[win32___________]=tclsh___________________________" #@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________" #@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________" #@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________" #@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________" #@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________" #@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________" #@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________" #@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________" #@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________" #@SET "nextshelltype[other___________]=tcl_____________"
#: </nextshell> #: <<nextshell_end>>
proc multishell {args} { proc multishell {args} {
set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell] set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell]
lassign [dict values $argd] leaders opts values received lassign [dict values $argd] leaders opts values received
@ -947,7 +1014,7 @@ namespace eval punk::mix::commandset::scriptwrap {
set startdir [pwd] set startdir [pwd]
set allowed_extensions [list tcl ps1 sh bash pl] set allowed_extensions [list tcl ps1 sh bash pl]
#TODO - distinct sections for sh vs bash? needs experiments.. #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 sh bash bash pl perl]
set extension_langs [list tcl tcl ps1 powershell sh shell bash shell 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 list_input_files [list]
set has_config 0
set configd [dict create] set configd [dict create]
if {$scriptset ne ""} { if {$scriptset ne ""} {
puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'" puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'"
#.toml file may or may not exist #.toml file may or may not exist
if {[file exists ${scriptset}_wrap.toml]} { 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 configd [_read_scriptset_wrap_tomlfile $scriptdir/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} { if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts] set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts { foreach s $configured_scripts {
@ -1008,6 +1077,7 @@ namespace eval punk::mix::commandset::scriptwrap {
return false return false
} }
} else { } else {
set configd [_default_configd]
puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml" puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml"
puts stdout "Will look for the following scripts in $scriptdir" puts stdout "Will look for the following scripts in $scriptdir"
foreach e $allowed_extensions { foreach e $allowed_extensions {
@ -1018,12 +1088,17 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptdir/$scriptset.$e lappend list_input_files $scriptdir/$scriptset.$e
} }
} }
dict set configd scripts $list_input_files
} }
} else { } else {
set configd [_default_configd]
#expect a single script #expect a single script
if {[file exists $specified_path]} { if {[file exists $specified_path]} {
lappend list_input_files $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}] set found_script [expr {[llength $list_input_files] > 0}]
@ -1061,8 +1136,9 @@ namespace eval punk::mix::commandset::scriptwrap {
if {$scriptset ne ""} { if {$scriptset ne ""} {
#.toml file may or may not exist #.toml file may or may not exist
if {[file exists $scriptroot/${scriptset}_wrap.toml]} { 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 configd [_read_scriptset_wrap_tomlfile $scriptroot/${scriptset}_wrap.toml]
set has_config 1
if {[dict exists $configd scripts]} { if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts] set configured_scripts [dict get $configd scripts]
foreach s $configured_scripts { foreach s $configured_scripts {
@ -1084,6 +1160,8 @@ namespace eval punk::mix::commandset::scriptwrap {
lappend list_input_files $scriptroot/$scriptset.$e lappend list_input_files $scriptroot/$scriptset.$e
} }
} }
set configd [_default_configd]
dict set configd scripts [lmap f $list_input_files {file tail $f}]
} }
} else { } else {
#expect a single script #expect a single script
@ -1094,6 +1172,8 @@ namespace eval punk::mix::commandset::scriptwrap {
} }
lappend list_input_files $scriptroot/$filepath_or_scriptset 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}] 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 #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] set templatename [dict get $configd template]
} else { } else {
if {$opt_template eq "\uFFFF"} { if {$opt_template eq "\uFFFF"} {
@ -1119,6 +1199,7 @@ namespace eval punk::mix::commandset::scriptwrap {
} else { } else {
set templatename $opt_template set templatename $opt_template
} }
dict set configd template $templatename
} }
set templatename_root [file rootname [file tail $templatename]] set templatename_root [file rootname [file tail $templatename]]
@ -1199,21 +1280,26 @@ namespace eval punk::mix::commandset::scriptwrap {
#todo #todo
#output_file extension may also depend on the template being used.. and/or the <scriptset>_wrap.toml config #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
package require platform set thisplatform [string tolower [platform::identify]]
set thisplatform [string tolower [platform::identify]] set ptype [lindex [split $thisplatform -] 0]
set ptype [lindex [split $thisplatform -] 0] switch -- $ptype {
switch -- $ptype { win32 - dragonflybsd - freebsd - netbsd - linux - macosx {}
win32 - dragonflybsd - freebsd - netbsd - linux - macosx {} default {
default { set ptype other
set ptype other
}
} }
}
if {$has_config} {
set out [dict get $configd $ptype outputfile] set out [dict get $configd $ptype outputfile]
set output_file [file join $output_folder $out] 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 { } else {
#no _wrap.toml file available #no _wrap.toml file available
if {$::tcl_platform(platform) eq "windows"} { if {$ptype eq "win32"} {
set output_extension .cmd set output_extension .cmd
} else { } else {
set output_extension .sh set output_extension .sh
@ -1224,6 +1310,11 @@ namespace eval punk::mix::commandset::scriptwrap {
set infile [lindex $list_input_files 0] set infile [lindex $list_input_files 0]
set output_file [file join $output_folder [file rootname [file tail $infile]]$output_extension] 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]" 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_force} {
if {$opt_askme} { 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"} { 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." 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 $objFile_existing destroy
@ -1264,92 +1355,122 @@ namespace eval punk::mix::commandset::scriptwrap {
puts stdout $ln puts stdout $ln
} }
puts stdout "-----------------------------------------------\n" 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 lang_data [dict create]
set filepath [lindex $list_input_files 0] foreach filepath $list_input_files {
set fdscript [open $filepath r] set script_ext [string trim [file extension $filepath] .]
fconfigure $fdscript -translation binary set lang [dict get $extension_langs [string tolower $script_ext]]
set script_data [read $fdscript] set fdscript [open $filepath r]
close $fdscript fconfigure $fdscript -translation binary
puts stdout "Read [string length $script_data] bytes of template data.." set script_data [read $fdscript]
set script_lines [split $script_data \n] close $fdscript
puts stdout "Displaying first 3 lines of your script between dashed lines..." puts stdout "Read [string length $script_data] bytes of template data for lang: $lang"
puts stdout "-----------------------------------------------" set script_lines [split $script_data \n]
foreach ln [lrange $script_lines 0 3] { dict set lang_data $lang $script_lines
puts stdout $ln puts stdout "Displaying first 3 lines of your script [file tail $filepath] between dashed lines..."
} puts stdout "[a green]$filepath[a]"
puts stdout "-----------------------------------------------\n" puts stdout "-----------------------------------------------"
puts stdout "Target for above script data is '$output_file'" foreach ln [lrange $script_lines 0 3] {
set script_ext [string trim [file extension $filepath] .] puts stdout $ln
set lang [dict get $extension_langs [string tolower $script_ext]] }
puts stdout "Language of script being wrapped is $lang" puts stdout "-----------------------------------------------\n"
if {$opt_askme} { puts stdout "Target for script data is '$output_file'"
set answer [util::askuser "Does this look correct? Y|N"] puts stdout "Language of script being wrapped is [a bold yellow]$lang[a]"
if {[string tolower $answer] ne "y"} { if {$opt_askme} {
puts stderr "mix new aborting due to user response '$answer' (required Y or y to proceed) use -askme 0 to avoid prompts." set answer [util::askuser "Does this look correct? Y|N"]
return if {[string tolower $answer] ne "y"} {
puts stderr "mix new aborting due to user response '$answer' (required Y or y to proceed) use -askme 0 to avoid prompts."
return
}
} }
} }
set start_idx 0 set template_ranges [list]
set end_idx 0 set data_items [list]
set line_idx 0 set trange [list 0]
set existing_payload [list] set line_idx -1
set opentag ""
foreach ln $template_lines { foreach ln $template_lines {
incr line_idx
if {[string match "#<$lang-pre-launch-subprocess>*" $ln]} { if {$opentag eq ""} {
set start_idx $line_idx if {[string match ": <<asadmin_start>>*" $ln]} {
} elseif {[string match "#</$lang-pre-launch-subprocess>*" $ln]} { set opentag asadmin
set end_idx $line_idx lset trange 1 $line_idx ;#include tag in template
break lappend template_ranges $trange
} elseif {$start_idx > 0} { set trange [list $line_idx]
if {$end_idx > 0} { set asadmin [dict get $configd as_admin]
lappend existing_payload [string trim $ln] 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 { } else {
switch -- [string range $opentag 0 6] {
asadmin {
if {[string match ": <<asadmin_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
nextshe {
if {[string match ": <<nextshell_end>>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
payload {
set lang [string range $opentag 8 end] ;#payload-xxx
if {[string match "#</$lang-payload>*" $ln]} {
set trange [list $line_idx]
set opentag ""
}
}
}
} }
incr line_idx
} }
if {($start_idx == 0) || ($end_idx == 0)} { if {$opentag eq ""} {
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" lset trange 1 end
lappend template_ranges $trange
} else {
error "multishell - unable to find closing tag for '$opentag'"
} }
set existing_string [join $existing_payload \n] set newscript ""
if {[string length [string trim $existing_string]]} { foreach trange $template_ranges item $data_items {
puts stdout "EXISTING <$lang-pre-launch-subprocess> PAYLOAD!!" append newscript [join [lrange $template_lines {*}$trange] \n]
puts stdout "-----------------------------------------------\n" if {$item ne ""} {
puts stdout $existing_string append newscript \n $item \n
puts stdout "-----------------------------------------------\n" } else {
error "wrap_in_multishell found existing payload for language $lang ... aborting." append newscript \n
#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
}
}
} }
} }
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 "New script is [string length $newscript] bytes"
puts stdout $newscript puts stdout $newscript
set fdtarget [open $output_file w] set fdtarget [open $output_file w]
fconfigure $fdtarget -translation binary fconfigure $fdtarget -translation binary
puts -nonewline $fdtarget $newscript 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^ : "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 = @' set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \ : 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 + \ : .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" + : "[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 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 It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################ @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 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 sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system @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: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath> @REM e.g from within a running punkshell: dev 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 Call with sh, bash, perl, or tclsh. (powershell untested on unix)
@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 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 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 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 @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 @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 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 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 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 @REM Supporting more explicit oses than those listed may also require script padding adjustment
: <nextshell> : <<nextshell_start>>
@SET "nextshellpath[win32___________]=tclsh___________________________" @SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
@SET "nextshelltype[win32___________]=tcl_____________" @SET "nextshelltype[win32___________]=tcl_____________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________" @SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[dragonflybsd____]=tcl_____________" @SET "nextshelltype[dragonflybsd____]=tcl_____________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________" @SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[freebsd_________]=tcl_____________" @SET "nextshelltype[freebsd_________]=tcl_____________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________" @SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[netbsd__________]=tcl_____________" @SET "nextshelltype[netbsd__________]=tcl_____________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________" @SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[linux___________]=tcl_____________" @SET "nextshelltype[linux___________]=tcl_____________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________" @SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[macosx__________]=tcl_____________" @SET "nextshelltype[macosx__________]=tcl_____________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________" @SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[other___________]=tcl_____________" @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). @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" @SET "asadmin=0"
: </asadmin> : <<asadmin_end>>
@REM @ECHO nextshelltype is %nextshelltype[win32___________]% @REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM @SET "selected_shelltype=%nextshelltype[win32___________]%" @REM @SET "selected_shelltype=%nextshelltype[win32___________]%"
@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 args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%" @ECHO Next >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%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%" %* @"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B @EXIT /B
@ -175,8 +175,11 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
COPY "%~dp0%~n0%~x0" "%~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 /? @REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "%selected_shelltype_trimmed%"=="powershell" ( @IF "!selected_shelltype_trimmed!"=="none" (
REM pws vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time 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 REM test availability of preferred option of powershell7+ pwsh
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL
SET pwshtest_exitcode=!errorlevel! SET pwshtest_exitcode=!errorlevel!
@ -192,7 +195,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
SET task_exitcode=!errorlevel! SET task_exitcode=!errorlevel!
) )
) ELSE ( ) ELSE (
IF "%selected_shelltype_trimmed%"=="wslbash" ( IF "!selected_shelltype_trimmed!"=="wslbash" (
CALL :getWslPath %winpath% wslpath CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%" REM ECHO wslfullpath "!wslpath!%fname%"
%selected_shellpath_trimmed% "!wslpath!%fname%" %arglist% %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 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 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 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; %selected_shellpath_trimmed% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
) ELSE ( ) ELSE (
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes% 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 "rtrn=%~2"
@SET "string=%~1" @SET "string=%~1"
@SET "trimstring=%~1" @SET "trimstring=%~1"
@REM trim up to 31 underscores from the end of a string using string substitution @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 trimstring=%trimstring:__###=###% @SET "trimstring=%trimstring:____###=###%"
@SET trimstring=%trimstring:_###=###% @SET "trimstring=%trimstring:__###=###%"
@SET trimstring=%trimstring:###=% @SET "trimstring=%trimstring:_###=###%"
@SET "trimstring=%trimstring:###=%"
@SET "result=!trimstring!" @SET "result=!trimstring!"
@ENDLOCAL & ( @ENDLOCAL & (
@IF "%~2" neq "" ( @IF "%~2" neq "" (
@ -439,7 +444,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
# -- e.g tclsh filename.cmd # -- 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 '@ Hide :exit_multishell;Hide {<#};Hide '@
namespace eval ::punk::multishell { namespace eval ::punk::multishell {
set last_script_root [file dirname [file normalize ${::argv0}/__]] set last_script_root [file dirname [file normalize ${::argv0}/__]]
@ -473,6 +478,9 @@ namespace eval ::punk::multishell {
#puts "argv0 : $::argv0" #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>
#</tcl-pre-launch-subprocess> #</tcl-pre-launch-subprocess>
@ -502,8 +510,20 @@ if {[::punk::multishell::is_main]} {
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload # -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload
# end hide from unix shells \ # end hide from unix shells \
HEREDOC1B_HIDE_FROM_BASH_AND_SH 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 \ # sh/bash \
shift && set -- "${@:1:$#-1}" 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. # -- This if block only needed if Tcl didn't exit or return above.
if false==false # else { if false==false # else {
@ -518,10 +538,80 @@ if false==false # else {
# -- if sh/bash scripting needs to run on windows too. # -- 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 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" #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>
#</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 #-- use exec to use exitcode (if any) directly from the tcl script
#exec /usr/bin/env tclsh "$0" "$@" #exec /usr/bin/env tclsh "$0" "$@"
#-- alternative - can run sh/bash script after the tcl call. #-- alternative - can run sh/bash script after the tcl call.
/usr/bin/env tclsh "$0" "$@" #/usr/bin/env tclsh "$0" "$@"
exitcode=$? #exitcode=$?
#echo "sh/bash reporting tcl exitcode: ${exitcode}" #echo "sh/bash reporting tcl exitcode: ${exitcode}"
#-- override exitcode example #-- override exitcode example
#exit 66 #exit 66
@ -558,8 +648,18 @@ exit ${exitcode}
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### # ## ### ### ### ### ### ### ### ### ### ### ### ### ###
=cut =cut
#!/user/bin/perl #!/user/bin/perl
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
my $exit_code = 0; 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; #use ExtUtils::Installed;
#my $installed = ExtUtils::Installed->new(); #my $installed = ExtUtils::Installed->new();
#my @modules = $installed->modules(); #my @modules = $installed->modules();
@ -571,13 +671,15 @@ my $exit_code = 0;
my $scriptname = $0;
print "perl $scriptname\n";
my $i =1; my $i =1;
foreach my $a(@ARGV) { foreach my $a(@ARGV) {
print "Arg # $i: $a\n"; 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>
#</perl-pre-launch-subprocess> #</perl-pre-launch-subprocess>
@ -585,7 +687,7 @@ foreach my $a(@ARGV) {
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<perl-launch-subprocess> #<perl-launch-subprocess>
$exit_code=system("tclsh", $scriptname, @ARGV); #$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code"; #print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess> #</perl-launch-subprocess>
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
@ -680,12 +782,34 @@ function GetDynamicParamDictionary {
# } # }
#} #}
#psmain @args #psmain @args
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host #"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host #"Script Name : {0}" -f $scriptname | write-host
#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host #"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host
#"powershell args : {0}" -f ($args -join ", ") | 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>
#</powershell-pre-launch-subprocess> #</powershell-pre-launch-subprocess>
@ -693,7 +817,7 @@ function GetDynamicParamDictionary {
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<powershell-launch-subprocess> #<powershell-launch-subprocess>
tclsh $scriptname $args #tclsh $scriptname $args
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host #"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess> #</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#>})" ^ : "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=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershell;proc Hide x {proc $x args {}}; Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @' set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. (close sqote for unix shells) ' \ : 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 \ : .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" : "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" +
: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH' : << '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 \ : 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 ############################################################################################################################
@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 It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################ @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 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 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 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 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 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 @SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh'" @SET "validshelltypes= powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________"
@SET "shells[10]=pwsh" @REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch
@SET "shells[11]=sh" @REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx
@set "shells[12]=bash" @REM The horrible underscore-padded fixed-widths are to keep the batch labels aligned whilst allowing values to be set
@SET "shells[13]=tclsh" @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> : <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> : </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). @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>
@SET "asadmin=0" @SET "asadmin=0"
: </asadmin> : </asadmin>
@REM nextshell set to index for validshells .eg 10 for pwsh @REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM @ECHO nextshell is %nextshell% @REM @SET "selected_shelltype=%nextshelltype[win32___________]%"
@SET "selected=!shells[%nextshell%]!" @SET "selected_shelltype=%nextshelltype[win32___________]%"
@REM @ECHO selected %selected% @REM @ECHO selected_shelltype %selected_shelltype%
@CALL SET "keyRemoved=%%validshells:'!selected!'=%%" @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 @ECHO keyremoved %keyRemoved%
@REM Note that 'powershell' e.g v5 is just a fallback for when pwsh is not available @REM Note that 'powershell' e.g v5 is just a fallback for when pwsh is not available
@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ### @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 -- 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 -- Even something as simple as adding or removing an @REM
@REM -- From within punkshell - use: @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 -- to check your templates or final wrapped scripts for byte boundary issues
@REM -- It will report any labels that are on boundaries @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 -- 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 -- 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 -- 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 -- '@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 -- 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 -- 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 ############################################################################################################################
@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections @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 "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs"
@SET arglist=%* @SET arglist=%*
@IF "%1"=="PUNK-ELEVATED" ( @SET "qstrippedargs=args%arglist%"
@SET "qstrippedargs=%qstrippedargs:"=%"
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
GOTO :gotPrivileges GOTO :gotPrivileges
) )
@IF !asadmin!==1 ( @IF !asadmin!==1 (
net file 1>NUL 2>NUL net file 1>NUL 2>NUL
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges ) @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 @GOTO skip_privileges
:getPrivileges :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 Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%"
@ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%" @ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%"
@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%" @ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%" @ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%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 @ECHO Launching script in new windows due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %* @"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B @EXIT /B
@ -113,7 +151,7 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@REM setlocal & pushd . @REM setlocal & pushd .
@PUSHD . @PUSHD .
@cd /d %~dp0 @cd /d %~dp0
@IF "%1"=="PUNK-ELEVATED" ( @IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
@DEL "%vbsGetPrivileges%" 1>nul 2>nul @DEL "%vbsGetPrivileges%" 1>nul 2>nul
@SET arglist=%arglist:~14% @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" ( @if not exist "%~dp0%~n0.ps1" (
@SET need_ps1=1 @SET need_ps1=1
) ELSE ( ) 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" @REM @ECHO "files same"
@SET need_ps1=0 @SET need_ps1=0
) )
@ -134,10 +172,10 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@SET need_ps1=1 @SET need_ps1=1
:pscontinue :pscontinue
@IF !need_ps1!==1 ( @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 /? @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 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 REM test availability of preferred option of powershell7+ pwsh
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL 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 ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 ( 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 ( ) ELSE (
REM CALL powershell -nop -nol -c write-host powershell-found REM CALL powershell -nop -nol -c write-host powershell-found
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %* 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! SET task_exitcode=!errorlevel!
) )
) ELSE ( ) ELSE (
IF "!shells[%nextshell%]!"=="bash" ( IF "%selected_shelltype_trimmed%"=="wslbash" (
CALL :getWslPath %winpath% wslpath CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%" 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 ( ) ELSE (
REM probably tclsh or sh REM perl or tcl or sh or bash
IF NOT "x%keyRemoved%"=="x%validshells%" ( 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 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 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 ( ) 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 SET task_exitcode=66
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
GOTO :exit_multishell GOTO :exit_multishell
) )
) )
) )
@REM batch file library functions @REM batch file library functions
@REM boundary padding
@GOTO :endlib @GOTO :endlib
:getWslPath :getWslPath
@ -179,7 +225,9 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@SET "name=%~nx1" @SET "name=%~nx1"
@SET "drive=%~d1" @SET "drive=%~d1"
@SET "rtrn=%~2" @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 & ( @ENDLOCAL & (
@if "%~2" neq "" ( @if "%~2" neq "" (
SET "%rtrn%=%result%" SET "%rtrn%=%result%"
@ -227,6 +275,7 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
) )
@EXIT /B @EXIT /B
@REM boundary padding @REM boundary padding
@REM boundary padding
:getNormalizedScriptTail :getNormalizedScriptTail
@SETLOCAL @SETLOCAL
@SET "result=%~nx0" @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 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
@REM boundary padding
@REM boundary padding
@SETLOCAL @SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash @CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash @CALL :stringContains %~1 "/" hasForwardSlash
@ -289,7 +340,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
) )
) )
@EXIT /B @EXIT /B
@REM boundary padding
@REM boundary padding
:stringToUpper :stringToUpper
@SETLOCAL @SETLOCAL
@SET "rtrn=%~2" @SET "rtrn=%~2"
@ -307,7 +359,47 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
) )
) )
@EXIT /B @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 :isNumeric
@SETLOCAL @SETLOCAL
@SET "notnumeric="&FOR /F "delims=0123456789" %%i in ("%1") do set "notnumeric=%%i" @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 :endlib
: \ : \
@REM padding
@REM padding
@REM @SET taskexit_code=!errorlevel! & goto :exit_multishell @REM @SET taskexit_code=!errorlevel! & goto :exit_multishell
@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 rename set ""; rename s set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@ Hide :exit_multishell;Hide {<#};Hide '@
namespace eval ::punk::multishell { 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]/__]] set last_script [file dirname [file normalize [info script]/__]]
if {[info exists argv0] && if {[info exists ::argv0] &&
$last_script eq $last_script_root $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 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)]} { if {![info exists ::punk::multishell::is_main($script_name)]} {
#e.g a .dll or something else unanticipated #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 "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 0
} }
return [set ::punk::multishell::is_main($script_name)] return [set ::punk::multishell::is_main($script_name)]
@ -380,10 +474,16 @@ namespace eval ::punk::multishell {
# -- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-payload> #<tcl-pre-launch-subprocess>
#</tcl-payload> #</tcl-pre-launch-subprocess>
#<tcl-launch-subprocess>
#</tcl-launch-subprocess>
#<tcl-post-launch-subprocess>
#</tcl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- ---
# -- Best practice is to always return or exit above, or just by leaving the below defaults in place. # -- Best practice is to always return or exit above, or just by leaving the below defaults in place.
@ -414,33 +514,33 @@ if false==false # else {
# -- leave as is if all that is required is launching the Tcl payload" # -- 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 # -- 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. # -- if sh/bash scripting needs to run on windows too.
# -- # --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### # ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload # -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
exitcode=0
#printf "start of bash or sh code" #printf "start of bash or sh code"
#<shell-payload-pre-tcl> #<shell-pre-launch-subprocess>
#</shell-payload-pre-tcl> #</shell-pre-launch-subprocess>
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<shell-launch-tcl> #<shell-launch-subprocess>
exitcode=0 ;#default assumption
#-- sh/bash launches Tcl here instead of shebang line at top #-- sh/bash launches Tcl here instead of shebang line at top
#-- use exec to use exitcode (if any) directly from the tcl script #-- use exec to use exitcode (if any) directly from the tcl script
#exec /usr/bin/env tclsh "$0" "$@" #exec /usr/bin/env tclsh "$0" "$@"
#-- alternative - can run sh/bash script after the tcl call. #-- alternative - can run sh/bash script after the tcl call.
/usr/bin/env tclsh "$0" "$@" /usr/bin/env tclsh "$0" "$@"
exitcode=$? exitcode=$?
#echo "tcl exitcode: ${exitcode}" #echo "sh/bash reporting tcl exitcode: ${exitcode}"
#-- override exitcode example #-- override exitcode example
#exit 66 #exit 66
#</shell-launch-tcl> #</shell-launch-subprocess>
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<shell-payload-post-tcl> #<shell-post-launch-subprocess>
#</shell-payload-post-tcl> #</shell-post-launch-subprocess>
#printf "sh/bash done \n" #printf "sh/bash done \n"
@ -448,7 +548,57 @@ exitcode=$?
#------------------------------------------------------ #------------------------------------------------------
fi fi
exit ${exitcode} 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 } # 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 # 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 # -- Do not edit if current file is the .ps1
# -- Edit the corresponding .cmd and it will autocopy # -- 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 # -- 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 } 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 # -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host #"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host #"Script Name : {0}" -f $scriptname | write-host
@ -470,22 +687,22 @@ $scriptname = getScriptName
#"powershell args : {0}" -f ($args -join ", ") | write-host #"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- --- # -- --- --- ---
#<powershell-payload-pre-tcl> #<powershell-pre-launch-subprocess>
#</powershell-payload-pre-tcl> #</powershell-pre-launch-subprocess>
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<powershell-launch-tcl> #<powershell-launch-subprocess>
tclsh $scriptname $args tclsh $scriptname $args
#</powershell-launch-tcl> #"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<powershell-payload-post-tcl> #<powershell-post-launch-subprocess>
#</powershell-payload-post-tcl> #</powershell-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload # -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
Exit $LASTEXITCODE Exit $LASTEXITCODE
# heredoc2 for powershell to ignore block below # heredoc2 for powershell to ignore block below
$1 = @' $1 = @'
@ -498,7 +715,7 @@ $1 = @'
: \ : \
@REM @ECHO exitcode: !task_exitcode! @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! @EXIT /B !task_exitcode!
# cmd has exited # cmd has exited
@ -509,6 +726,7 @@ $1 = @'
# -- powershell multiline comment # -- powershell multiline comment
#> #>
<# <#
no script engine should try to run me
# id:tailblock1 # id:tailblock1
# <ctrl-z> # <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^ : "[rename set s;proc Hide x {proc $x args {}};Hide :]" "\$(function : {<#pwsh#>})" ^
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @' 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 + ' \ : 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 + \ : .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" + : "[Hide @ECHO; Hide ); Hide (;Hide echo; Hide @REM]#not necessary but can help avoid errs in testing"
: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH' : << '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 \ : 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 ############################################################################################################################
@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, 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 It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################ @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 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 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 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 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 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 @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[10]=pwsh"
@SET "shells[11]=sh" @SET "shells[11]=sh"
@set "shells[12]=bash" @set "shells[12]=bash"
@SET "shells[13]=tclsh" @SET "shells[13]=tclsh"
@SET "shells[14]=perl"
: <nextshell> : <nextshell>
@SET "nextshell=13" @SET "nextshell=13"
: </nextshell> : </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 -- 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 -- Even something as simple as adding or removing an @REM
@REM -- From within punkshell - use: @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 -- to check your templates or final wrapped scripts for byte boundary issues
@REM -- It will report any labels that are on boundaries @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 -- 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 -- 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 -- 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 -- '@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 -- 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 -- 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 ############################################################################################################################
@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections @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 "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs"
@SET arglist=%* @SET arglist=%*
@SET "qstrippedargs=args%arglist%" @IF "%1"=="PUNK-ELEVATED" (
@SET "qstrippedargs=%qstrippedargs:"=%"
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
GOTO :gotPrivileges GOTO :gotPrivileges
) )
@IF !asadmin!==1 ( @IF !asadmin!==1 (
net file 1>NUL 2>NUL net file 1>NUL 2>NUL
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges ) @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 @GOTO skip_privileges
:getPrivileges :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 Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%"
@ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%" @ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%"
@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%" @ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%" @ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%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 @ECHO Launching script in new windows due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %* @"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B @EXIT /B
@ -136,7 +113,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@REM setlocal & pushd . @REM setlocal & pushd .
@PUSHD . @PUSHD .
@cd /d %~dp0 @cd /d %~dp0
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" ( @IF "%1"=="PUNK-ELEVATED" (
@DEL "%vbsGetPrivileges%" 1>nul 2>nul @DEL "%vbsGetPrivileges%" 1>nul 2>nul
@SET arglist=%arglist:~14% @SET arglist=%arglist:~14%
) )
@ -147,7 +124,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@if not exist "%~dp0%~n0.ps1" ( @if not exist "%~dp0%~n0.ps1" (
@SET need_ps1=1 @SET need_ps1=1
) ELSE ( ) 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" @REM @ECHO "files same"
@SET need_ps1=0 @SET need_ps1=0
) )
@ -157,7 +134,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@SET need_ps1=1 @SET need_ps1=1
:pscontinue :pscontinue
@IF !need_ps1!==1 ( @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 /? @REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "!shells[%nextshell%]!"=="pwsh" ( @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 ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 ( IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% & SET task_exitcode=!errorlevel!
SET task_exitcode=!errorlevel!
) ELSE ( ) ELSE (
REM CALL powershell -nop -nol -c write-host powershell-found REM CALL powershell -nop -nol -c write-host powershell-found
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %* 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" ( IF "!shells[%nextshell%]!"=="bash" (
CALL :getWslPath %winpath% wslpath CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%" REM ECHO wslfullpath "!wslpath!%fname%"
!shells[%nextshell%]! "!wslpath!%fname%" %arglist% !shells[%nextshell%]! "!wslpath!%fname%" %arglist% & SET task_exitcode=!errorlevel!
SET task_exitcode=!errorlevel!
) ELSE ( ) ELSE (
REM probably tclsh or sh REM probably tclsh or sh
IF NOT "x%keyRemoved%"=="x%validshells%" ( 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 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 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% !shells[%nextshell%]! "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel!
SET task_exitcode=!errorlevel!
) ELSE ( ) ELSE (
ECHO %fname% has invalid nextshell value ^(%nextshell%^) !shells[%nextshell%]! valid options are %validshells% ECHO %fname% has invalid nextshell value ^(%nextshell%^) !shells[%nextshell%]! valid options are %validshells%
SET task_exitcode=66 SET task_exitcode=66
@REM boundary padding
@REM boundary padding
GOTO :exit_multishell GOTO :exit_multishell
) )
) )
) )
@REM batch file library functions @REM batch file library functions
@REM boundary padding
@GOTO :endlib @GOTO :endlib
:getWslPath :getWslPath
@ -256,7 +227,6 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
) )
@EXIT /B @EXIT /B
@REM boundary padding @REM boundary padding
@REM boundary padding
:getNormalizedScriptTail :getNormalizedScriptTail
@SETLOCAL @SETLOCAL
@SET "result=%~nx0" @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 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
@REM boundary padding
@REM boundary padding
@SETLOCAL @SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash @CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash @CALL :stringContains %~1 "/" hasForwardSlash
@ -412,15 +380,9 @@ namespace eval ::punk::multishell {
# -- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-pre-launch-subprocess> #<tcl-payload>
#</tcl-pre-launch-subprocess> #</tcl-payload>
#<tcl-launch-subprocess>
#</tcl-launch-subproces>
#<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" # -- 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 # -- 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. # -- if sh/bash scripting needs to run on windows too.
# -- # --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### # ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload # -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
exitcode=0
#printf "start of bash or sh code" #printf "start of bash or sh code"
#<shell-pre-launch-subprocess> #<shell-payload-pre-tcl>
#</shell-pre-launch-subprocess> #</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 #-- sh/bash launches Tcl here instead of shebang line at top
#-- use exec to use exitcode (if any) directly from the tcl script #-- use exec to use exitcode (if any) directly from the tcl script
#exec /usr/bin/env tclsh "$0" "$@" #exec /usr/bin/env tclsh "$0" "$@"
#-- alternative - can run sh/bash script after the tcl call. #-- alternative - can run sh/bash script after the tcl call.
/usr/bin/env tclsh "$0" "$@" /usr/bin/env tclsh "$0" "$@"
exitcode=$? exitcode=$?
#echo "sh/bash reporting tcl exitcode: ${exitcode}" #echo "tcl exitcode: ${exitcode}"
#-- override exitcode example #-- override exitcode example
#exit 66 #exit 66
#</shell-launch-subprocess> #</shell-launch-tcl>
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<shell-post-launch-subprocess> #<shell-payload-post-tcl>
#</shell-post-launch-subproces> #</shell-payload-post-tcl>
#printf "sh/bash done \n" #printf "sh/bash done \n"
@ -486,57 +448,7 @@ exitcode=$?
#------------------------------------------------------ #------------------------------------------------------
fi fi
exit ${exitcode} 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 } # 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 # 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 # -- Do not edit if current file is the .ps1
# -- Edit the corresponding .cmd and it will autocopy # -- 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 # -- 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 } 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 # -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host #"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host #"Script Name : {0}" -f $scriptname | write-host
@ -625,22 +470,22 @@ function GetDynamicParamDictionary {
#"powershell args : {0}" -f ($args -join ", ") | write-host #"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- --- # -- --- --- ---
#<powershell-pre-launch-subprocess> #<powershell-payload-pre-tcl>
#</powershell-pre-launch-subprocess> #</powershell-payload-pre-tcl>
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<powershell-launch-subprocess> #<powershell-launch-tcl>
tclsh $scriptname $args tclsh $scriptname $args
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host #</powershell-launch-tcl>
#</powershell-launch-subprocess>
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
#<powershell-post-launch-subprocess> #<powershell-payload-post-tcl>
#</powershell-post-launch-subprocess> #</powershell-payload-post-tcl>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload # -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
Exit $LASTEXITCODE Exit $LASTEXITCODE
# heredoc2 for powershell to ignore block below # heredoc2 for powershell to ignore block below
$1 = @' $1 = @'
@ -653,7 +498,7 @@ $1 = @'
: \ : \
@REM @ECHO exitcode: !task_exitcode! @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! @EXIT /B !task_exitcode!
# cmd has exited # cmd has exited
@ -664,7 +509,6 @@ $1 = @'
# -- powershell multiline comment # -- powershell multiline comment
#> #>
<# <#
no script engine should try to run me
# id:tailblock1 # id:tailblock1
# <ctrl-z> # <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]" dict set map %marker% "[punk::ansi::a bold cyan]MERGEDBLOCK[punk::ansi::a]"
puts $outc [string map $map $opt_blocksep] puts $outc [string map $map $opt_blocksep]
} }
set blockresult ""
if {$opt_shrink_textfree_blocks} { if {$opt_shrink_textfree_blocks} {
set teststripped [punk::ansi::ansistrip $mergedblock] set teststripped [punk::ansi::ansistrip $mergedblock]
if {[string trim $teststripped] ne ""} { if {[string trim $teststripped] ne ""} {

2
src/vfs/_vfscommon.vfs/modules/punk/zip-0.1.1.tm

@ -400,7 +400,7 @@ tcl::namespace::eval punk::zip {
#todo - if it was internal preamble - need to adjust offsets to fix the split off zipfile #todo - if it was internal preamble - need to adjust offsets to fix the split off zipfile
set zout [open $outfile_zip w] set zout [open $outfile_zip w]
fconfigure $zout -encoding iso8859-1 -translation binary fconfigure $zout -encoding iso8859-1 -translation binary
chan copy $inzip $zout chan copy $inzip $zout
close $zout close $zout
} }
close $inzip close $inzip

Loading…
Cancel
Save