Browse Source

update multishell wrapper and punk::mix::commandset::scriptwrap to support longer nextshellpath + more shell capability

master
Julian Noble 1 week ago
parent
commit
9e20b9d437
  1. 2718
      src/bootsupport/modules/flagfilter-0.3.1.tm
  2. 5
      src/bootsupport/modules/punk/char-0.1.0.tm
  3. 16
      src/bootsupport/modules/punk/mix/commandset/scriptwrap-0.1.0.tm
  4. 2
      src/bootsupport/modules/punk/repl-0.1.2.tm
  5. 44
      src/bootsupport/modules/punk/zip-0.1.1.tm
  6. 2
      src/bootsupport/modules/shellrun-0.1.1.tm
  7. 16
      src/modules/punk/mix/commandset/scriptwrap-999999.0a1.0.tm
  8. 546
      src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd
  9. 2718
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/flagfilter-0.3.1.tm
  10. 5
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/char-0.1.0.tm
  11. 16
      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/repl-0.1.2.tm
  13. 44
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/zip-0.1.1.tm
  14. 2
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/shellrun-0.1.1.tm
  15. 2718
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/flagfilter-0.3.1.tm
  16. 5
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/char-0.1.0.tm
  17. 16
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/scriptwrap-0.1.0.tm
  18. 2
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm
  19. 44
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/zip-0.1.1.tm
  20. 2
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/shellrun-0.1.1.tm
  21. 16
      src/vfs/_vfscommon.vfs/modules/punk/mix/commandset/scriptwrap-0.1.0.tm
  22. 544
      src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd

2718
src/bootsupport/modules/flagfilter-0.3.1.tm

File diff suppressed because it is too large Load Diff

5
src/bootsupport/modules/punk/char-0.1.0.tm

@ -186,8 +186,9 @@ tcl::namespace::eval punk::char {
set r [list "" {*}[split $lowbits ""] $ridx {*}$charlist]
$t add_row $r
}
puts stderr $t
$t print
set result [$t print]
$t destroy
return $result
}
#just the 7-bit ascii. use [page ascii] for the 8-bit layout

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

@ -916,19 +916,19 @@ namespace eval punk::mix::commandset::scriptwrap {
return $configd
}
proc _get_nextshell_script {configd} {
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________________________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________"
#delimeters
@ -941,7 +941,7 @@ namespace eval punk::mix::commandset::scriptwrap {
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 n [expr {128 - [string length $path]}]
set _path [string repeat _ $n]
set type [dict get $v nextshelltype]
set n [expr {16 - [string length $type]}]

2
src/bootsupport/modules/punk/repl-0.1.2.tm

@ -2418,6 +2418,8 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
#if {[lindex $command 0] eq "runx"} {}
#temporary hack.
#todo - use happy path return options for non-primary result (like www package) ?
if {
[string equal -length [string length "d/ "] "d/ " $commandstr] || \
[string equal "d/\n" $commandstr] || \

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

@ -283,9 +283,47 @@ tcl::namespace::eval punk::zip {
#if there is an external preamble - extract that. (if there is also an internal preamble - ignore and consider part of the archive-data)
#Otherwise extract an internal preamble.
#if neither -
#if neither -?
#review - reconsider auto-determination of internal vs external preamble
proc extract_preamble {infile outfile_preamble {outfile_zip ""}} {
punk::args::define {
@id -id ::punk::zip::extract_preamble
@cmd -name punk::zip::extract_preamble -help\
"Split a zipfs based executable or library into its constituent
binary and zip parts.
Note that the binary preamble might be either 'within' the zip offsets,
or simply catenated prior to an unadjusted zip.
Some build processes may have 'adjusted' the zip offsets to make the zip cover the entire file
('file based' offset) whilst the more modern approach is to simply concatenate the binary and the zip
('archive based' offset). An archive-based offset is simpler and more reliably points to the proper
split location. It also allows 'zipfs info //zipfs:/app' to return the correct offset information.
Either way, extract_preamble can usually separate them, but in the unusual case that there is both an
external preamble and a preamble within the zip, only the external preamble will be split, with the
internal one remaining in the zip.
The inverse of this process would be to extract the .zip file created by this split to a folder,
e.g extracted_zip_folder (adjusting contents as required) and then to run:
zipfs mkimg newbinaryname.exe extracted_zip_folder <prefix> \"\" <extracted_preamble_or_alternative exe>
"
@values -min 2 -max 3
infile -type file -optional 0 -help\
"Name of existing tcl executable or shared lib with attached zipfs filesystem"
outfile_preamble -optional 0 -type file -help\
"Name of output file for binary preamble to be extracted to.
If this file already exists, an error will be raised"
outfile_zip -default "" -type file -help\
"Name of output file for zip data to be extracted to.
If this file already exists, an error will be raised"
}
proc extract_preamble {args} {
set argd [punk::args::parse $args withid ::punk::zip::extract_preamble]
lassign [dict values $argd] leaders opts values received
set infile [dict get $values infile]
set outfile_preamble [dict get $values outfile_preamble]
set outfile_zip [dict get $values outfile_zip]
set inzip [open $infile r]
fconfigure $inzip -encoding iso8859-1 -translation binary
if {[file exists $outfile_preamble]} {
@ -346,7 +384,7 @@ tcl::namespace::eval punk::zip {
#we could scan from top (ugly) - and with binary prefixes we could get false positives in the data that look like PK\3\4 headers
#we could either work out the format for all possible executables that could be appended (across all platforms) and understand where they end?
#or we just look for the topmost PK\3\4 header pointed to by a CDR record - and assume the CDR is complete
#step one - read all the CD records and find the highest pointed to local file record (which isn't necessarily the first - but should get us above most if not all of the zip data)
#we can't assume they're ordered in any particular way - so we in theory have to look at them all.
set baseoffset "unknown"

2
src/bootsupport/modules/shellrun-0.1.1.tm

@ -427,7 +427,7 @@ namespace eval shellrun {
cmdarg -type any -multiple 1 -optional 1
}]
proc runerr {args} {
set argd [punk::args::parse $args withid ::shellrun::runout]
set argd [punk::args::parse $args withid ::shellrun::runerr]
lassign [dict values $argd] leaders opts values received
if {[dict exists $received "-nonewline"]} {

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

@ -916,19 +916,19 @@ namespace eval punk::mix::commandset::scriptwrap {
return $configd
}
proc _get_nextshell_script {configd} {
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________________________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________"
#delimeters
@ -941,7 +941,7 @@ namespace eval punk::mix::commandset::scriptwrap {
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 n [expr {128 - [string length $path]}]
set _path [string repeat _ $n]
set type [dict get $v nextshelltype]
set n [expr {16 - [string length $type]}]

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

@ -95,19 +95,19 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
@REM If more than 64 chars needed for a target, it can still be done but overall script padding may need checking/adjusting
@REM Supporting more explicit oses than those listed may also require script padding adjustment
: <<nextshell_start>>
@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
@SET "nextshellpath[win32___________]=tclsh___________________________________________________________________________________________________________________________"
@SET "nextshelltype[win32___________]=tcl_____________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
@SET "nextshelltype[dragonflybsd____]=tcl_____________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
@SET "nextshelltype[freebsd_________]=tcl_____________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
@SET "nextshelltype[netbsd__________]=tcl_____________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
@SET "nextshelltype[linux___________]=tcl_____________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
@SET "nextshelltype[macosx__________]=tcl_____________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
@SET "nextshelltype[other___________]=tcl_____________"
: <<nextshell_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).
@ -119,7 +119,7 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
@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 :stringTrimTrailingUnderscores "%selected_shellpath%" selected_shellpath_trimmed
@CALL SET "keyRemoved=%%validshelltypes:!selected_shelltype!=%%"
@REM @ECHO keyremoved %keyRemoved%
@REM Note that 'powershell' e.g v5 is just a fallback for when pwsh is not available
@ -151,6 +151,7 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
@SET "winpath=%~dp0" %= e.g c:\punkshell\bin\ %=
@SET "fname=%~nx0"
@SET "scriptrootname=%~dp0%~n0" %= e.g c:\punkshell\bin\runtime (full path without extension) unavailable after shift, so store it =%
@SET "fullscriptname=%~dp0%~n0%~x0"
@REM @ECHO fname %fname%
@REM @ECHO winpath %winpath%
@REM @ECHO commandlineascalled %0
@ -162,6 +163,62 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
@CALL :stringToUpper %~nx0 capscripttail
@REM @ECHO capscriptname: %capscripttail%
@goto skip_parameter_wrangling
@set argCount=30
@rem This is the max number of args we are willing to handle. also bounded by approx 8k char limit of cmd.exe
@rem We do not loop over %* to count args as it is brittle for some inputs e.g will always skip cmd.exe separators e.g comma and semicolon
@rem Set argCount higher if desired, but there is a small amount of additional looping overhead.
@set tmpfile_base=%TEMP%\punkbatch_params
@call :getUniqueFile %tmpfile_base% ".txt" paramfile
@echo %paramfile%
%= NOTE when we loop like this using the percent-n args and shift, we lose unquoted separators such as comma and semicolon %=
@rem https://stackoverflow.com/questions/26551/how-can-i-pass-arguments-to-a-batch-file/5493124#5493124
@rem outer loop required to redirect all rem lines at once to file
@for %%x in (1) do @(
@for /L %%f in (1,1,%argCount%) do @(
@set "argnum=%%~nf"
@set "a1=%%1"
@rem @set "argname=%%!argnum!"
@rem @echo argname: !argname!
@call :rem_output !argnum! !a1!
@shift
)
) > %paramfile%
@echo off
@set "newcommandline= "
@(set target=cmd_pwsh)
@if "%target%"=="cmd_pwsh" (
@for /F "delims=" %%L in (%paramfile%) do @(
SETLOCAL DisableDelayedExpansion
set "param=%%L"
@REM @echo ######### %%L
@rem call :buildcmdline newcommandline param "{" "}"
@rem call :buildcmdline newcommandline param ' ' %= cmd.exe /c powershell ... -c %=
call :buildcmdline newcommandline param %= cmd.exe /c powershell ... -f %=
@rem @echo .
)
) ELSE (
@for /F "delims=" %%L in (%paramfile%) do @(
SETLOCAL DisableDelayedExpansion
set "param=%%L"
call :buildcmdline newcommandline param
)
)
@REM padding
SETLOCAL EnableDelayedExpansion
@echo off
@IF EXIST %paramfile% (
@DEL /F /Q %paramfile%
)
@IF EXIST %paramfile% (
echo failed to delete %paramfile%
cat %paramfile%
)
:skip_parameter_wrangling
@IF "%nftail%"=="%capscripttail%" (
@ECHO forcing asadmin=1 due to file name on filesystem being uppercase
@SET "asadmin=1"
@ -189,31 +246,112 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
: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 pre = "/c %fullscriptname% PUNK-ELEVATED " >> "%vbsGetPrivileges%"
@REM @echo pre = "PUNK-ELEVATED " >> "%vbsGetPrivileges%"
@echo args = pre >> "%vbsGetPrivileges%"
@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO args = args ^& Chr(34) ^& strArg ^& Chr(34) ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script in new window due to administrator elevation
@GOTO skiptest
%= Option Explicit =%
%= We need a child process to locate the current script. =%
@ECHO Const FLAG_PROCESS = "winver.exe" >> "%vbsGetPrivileges%"
%= ' WMI constants %=
@ECHO Const wbemFlagForwardOnly = 32 >> "%vbsGetPrivileges%"
%=' Generate a unique value to be used as a flag =%
@ECHO Dim guid >> "%vbsGetPrivileges%
@ECHO guid = Left(CreateObject("Scriptlet.TypeLib").GUID,38) >> "%vbsGetPrivileges%"
%= ' Start a process using the indicated flag inside its command line =%
@ECHO WScript.CreateObject("WScript.Shell").Run """" ^& FLAG_PROCESS ^& """ " ^& guid, 0, False >> "%vbsGetPrivileges%"
%= ' To retrieve process information a WMI reference is needed =%
@ECHO Dim wmi >> "%vbsGetPrivileges%"
@ECHO Set wmi = GetObject("winmgmts:{impersonationLevel=impersonate}^!\\.\root\cimv2") >> "%vbsGetPrivileges%"
%= ' Query the list of processes with the flag in its command line, retrieve the =%
%= ' process ID of its parent process ( our script! ) and terminate the process =%
@ECHO Dim colProcess, process, myProcessID >> "%vbsGetPrivileges%"
@ECHO Set colProcess = wmi.ExecQuery( _>> "%vbsGetPrivileges%"
@ECHO "SELECT ParentProcessID From Win32_Process " ^& _>> "%vbsGetPrivileges%"
@ECHO "WHERE Name='" ^& FLAG_PROCESS ^& "' " ^& _>> "%vbsGetPrivileges%"
@ECHO "AND CommandLine LIKE '%%" ^& guid ^& "%%'" _>> "%vbsGetPrivileges%"
@ECHO ,"WQL" , wbemFlagForwardOnly _>> "%vbsGetPrivileges%"
@ECHO ) >> "%vbsGetPrivileges%"
@ECHO For Each process In colProcess >> "%vbsGetPrivileges%"
@ECHO myProcessID = process.ParentProcessID >> "%vbsGetPrivileges%"
@ECHO process.Terminate >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%"
%= ' Knowing the process id of our script we can query the process list =%
%= ' and retrieve its command line =%
@ECHO Dim commandLine >> "%vbsGetPrivileges%"
@ECHO set colProcess = wmi.ExecQuery( _>> "%vbsGetPrivileges%"
@ECHO "SELECT CommandLine From Win32_Process " ^& _>> "%vbsGetPrivileges%"
@ECHO "WHERE ProcessID=" ^& myProcessID _>> "%vbsGetPrivileges%"
@ECHO ,"WQL" , wbemFlagForwardOnly _>> "%vbsGetPrivileges%"
@ECHO ) >> "%vbsGetPrivileges%"
@ECHO For Each process In colProcess >> "%vbsGetPrivileges%"
@ECHO commandLine = process.CommandLine >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%"
@ECHO WScript.Echo "raw commandline: " ^& commandLine >>"%vbsGetPrivileges%"
%= ' Done =%
@ECHO intpos = 0 >> "%vbsGetPrivileges%"
@ECHO intCount = 0 >> "%vbsGetPrivileges%"
@ECHO intstartsearch = 1 >> "%vbsGetPrivileges%"
@ECHO intmax = 100 >> "%vbsGetPrivileges%"
@ECHO do While intCount ^< 4 and intmax ^> 0 >> "%vbsGetPrivileges%"
@ECHO intpos = InStr(intstartsearch, commandline, """") >> "%vbsGetPrivileges%"
@ECHO if intpos ^<^> 0 then >> "%vbsGetPrivileges%"
@ECHO intCount = intCount + 1 >> "%vbsGetPrivileges%"
@ECHO if intcount = 4 then >> "%vbsGetPrivileges%"
@ECHO ' wscript.echo "position: " ^& intpos >> "%vbsGetPrivileges%"
@ECHO commandline = Mid(commandline,intpos+1) >> "%vbsGetPrivileges%"
@ECHO exit do >> "%vbsGetPrivileges%"
@ECHO else >> "%vbsGetPrivileges%"
@ECHO intstartsearch = intpos + 1 >> "%vbsGetPrivileges%"
@ECHO end if >> "%vbsGetPrivileges%"
@ECHO end if >> "%vbsGetPrivileges%"
@ECHO intmax = intmax -1 >> "%vbsGetPrivileges%"
@ECHO Loop >> "%vbsGetPrivileges%"
@ECHO if intcount ^< 4 then >> "%vbsGetPrivileges%"
@ECHO err.raise vbObjectError + 1001, "vbsGetPrivileges", "failed to parse commandline" >> "%vbsGetPrivileges%"
@ECHO end if >> "%vbsGetPrivileges%"
@ECHO commandline = pre ^& commandline >> "%vbsGetPrivileges%"
@ECHO WScript.Echo "commandline: " ^& commandLine >>"%vbsGetPrivileges%"
@ECHO WScript.Echo "args: " ^& args >>"%vbsGetPrivileges%"
:skiptest
@ECHO UAC.ShellExecute "cmd.exe", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@REM @ECHO UAC.ShellExecute "%fullscriptname%", commandline, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script "%fullscriptname%" in new window due to administrator elevation with args: "%*"
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@REM @"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" !newcommandline!
@EXIT /B
@REM buffer
@REM buffer
:gotPrivileges
@REM setlocal & pushd .
@PUSHD .
@cd /d %~dp0
@cd /d %winpath%
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
@DEL "%vbsGetPrivileges%" 1>nul 2>nul
@SET arglist=%arglist:~14%
@SHIFT
)
: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" (
@if not exist "%scriptrootname%.ps1" (
@SET need_ps1=1
) ELSE (
fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different
fc "%fullscriptname%" "%scriptrootname%.ps1" >nul || goto different
@REM @ECHO "files same"
@SET need_ps1=0
)
@ -223,74 +361,13 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
@SET need_ps1=1
:pscontinue
@IF !need_ps1!==1 (
COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL
COPY "%fullscriptname%" "%scriptrootname%.ps1" >NUL
)
@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "!selected_shelltype_trimmed!"=="none" (
SET selected_shelltype_trimmed=pwsh
)
@set argCount=30
@rem This is the max number of args we are willing to handle. also bounded by approx 8k char limit of cmd.exe
@rem We do not loop over %* to count args as it is brittle for some inputs e.g will always skip cmd.exe separators e.g comma and semicolon
@rem Set argCount higher if desired, but there is a small amount of additional looping overhead.
@set tmpfile_base=%TEMP%\punkbatch_params
@call :getUniqueFile %tmpfile_base% ".txt" paramfile
@echo %paramfile%
%= NOTE when we loop like this using the percent-n args, we lose unquoted separators such as comma and semicolon %=
@rem https://stackoverflow.com/questions/26551/how-can-i-pass-arguments-to-a-batch-file/5493124#5493124
@rem outer loop required to redirect all rem lines at once to file
@for %%x in (1) do @(
@for /L %%f in (1,1,%argCount%) do @(
@set "argnum=%%~nf"
@set "a1=%%1"
@rem @set "argname=%%!argnum!"
@rem @echo argname: !argname!
@call :rem_output !argnum! !a1!
@shift
)
) > %paramfile%
@echo off
@set "newcommandline= "
@(set target=cmd_pwsh)
@if "%target%"=="cmd_pwsh" (
@for /F "delims=" %%L in (%paramfile%) do @(
SETLOCAL DisableDelayedExpansion
set "param=%%L"
@REM @echo ######### %%L
@rem call :buildcmdline newcommandline param "{" "}"
@rem call :buildcmdline newcommandline param ' ' %= cmd.exe /c powershell ... -c %=
call :buildcmdline newcommandline param %= cmd.exe /c powershell ... -f %=
@rem @echo .
)
) ELSE (
@for /F "delims=" %%L in (%paramfile%) do @(
SETLOCAL DisableDelayedExpansion
set "param=%%L"
call :buildcmdline newcommandline param
)
)
@REM padding
SETLOCAL EnableDelayedExpansion
@echo off
@IF EXIST %paramfile% (
@DEL /F /Q %paramfile%
)
@IF EXIST %paramfile% (
echo failed to delete %paramfile%
cat %paramfile%
)
@REM @SET "squoted_args="
@REM @for %%a in (%*) do @(
@REM set "v=%%a"
@ -311,19 +388,24 @@ SETLOCAL EnableDelayedExpansion
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
@rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%scriptrootname%.ps1" %arglist%
@rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted
cmd /c pwsh -nop -nol -ExecutionPolicy bypass -f "%scriptrootname%.ps1" !newcommandline!
@rem pwsh -nop -nologo -ExecutionPolicy bypass -f "%scriptrootname%.ps1" %arglist% %= ok =%
@rem cmd /c pwsh -nop -nologo -ExecutionPolicy bypass -f "%scriptrootname%.ps1" !newcommandline!
!selected_shellpath_trimmed! "%scriptrootname%.ps1" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
REM TODO prompt user with option to call script to install pwsh using winget
@rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
cmd /c powershell -nop -nol -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline!
%= powershell with -file flag treats it's arguments differently to pwsh - we need cmd /c to preserve args with spaces =%
cmd /c powershell -nop -nologo -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" %arglist%
@rem cmd /c powershell -nop -nologo -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "!selected_shelltype_trimmed!"=="powershell" (
@rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
cmd /c powershell -nop -nol -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline!
%= powershell with -file flag treats it's arguments differently to pwsh - we need cmd /c to preserve args with spaces =%
@rem @echo powershell - !selected_shellpath_trimmed! "%scriptrootname%.ps1" %arglist%
@rem cmd /c powershell -nop -nologo -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" %arglist% %= ok - this works =%
!selected_shellpath_trimmed! "%scriptrootname%.ps1" %arglist%
@rem cmd /c powershell -nop -nologo -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
) ELSE (
IF "!selected_shelltype_trimmed!"=="wslbash" (
@ -337,24 +419,23 @@ SETLOCAL EnableDelayedExpansion
REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode
@ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!"
%selected_shellpath_trimmed% "%winpath%%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
@REM @ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!"
!selected_shellpath_trimmed! "%winpath%%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
) ELSE (
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
SET task_exitcode=66
@REM boundary padding
GOTO :exit_multishell
)
)
)
)
@REM batch file library functions
@GOTO :endlib
@REM padding
@REM padding
@REM padding
@REM padding
%= ---------------------------------------------------------------------- =%
@rem courtesy of dbenham
@ -458,6 +539,7 @@ do if not defined param1 set %%~"param1=%2%%~"
rem %1 #%2#
@exit /b
@rem padding
@REM courtesy of: https://stackoverflow.com/users/463115/jeb
:strlen stringVar returnVar
@(
@ -506,6 +588,8 @@ do if not defined param1 set %%~"param1=%2%%~"
)
@EXIT /B
@REM padding
@REM padding
: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
@ -545,6 +629,7 @@ do if not defined param1 set %%~"param1=%2%%~"
@EXIT /B
@REM boundary padding
@REM boundary padding
@REM boundary padding
:getNormalizedScriptTail
@SETLOCAL
@SET "result=%~nx0"
@ -654,14 +739,14 @@ do if not defined param1 set %%~"param1=%2%%~"
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
:stringTrimTrailingUnderscores
@SETLOCAL
@SET "rtrn=%~2"
@SET "string=%~1"
@SET "trimstring=%~1"
@REM trim up to 63 underscores from the end of a string using string substitution
@REM trim up to 127 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:________###=###%"
@ -707,10 +792,11 @@ do if not defined param1 set %%~"param1=%2%%~"
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- 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
# -- It is tuned to run (and possibly divert to different payload shell) when called from cmd.exe as a batch file, tclsh,sh,zsh,bash,perl or pwsh/powershell script
# -- i.e it is a polyglot file.
# -- The payload target (by os) is defined in the nextshell block at the top which is constructed when generating the polyglot
# -- using the tcl 'dev scriptwrap.multishell' command in a tcl punk shell
# -- The payload can be tcl,perl,powershell/pwsh or zsh/bash.
# -- The specific layout including some lines that appear just as comments is quite sensitive to change.
# -- It can be called on unix or windows platforms with or without the interpreter being specified on the commandline.
# -- e.g ./scriptname.cmd in sh or zsh or bash
@ -720,7 +806,13 @@ do if not defined param1 set %%~"param1=%2%%~"
rename set ""; rename S set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@
#---------------------------------------------------------------------
puts "info script : [info script]"
#puts "argcount : $::argc"
#puts "argvalues: $::argv"
#puts "argv0 : $::argv0"
# -- --- --- --- --- --- --- --- --- --- --- ---
#divert to configured nextshell
set script_as_called [info script]
package require platform
set plat_full [platform::generic]
set plat [lindex [split $plat_full -] 0]
@ -733,6 +825,14 @@ set in_data 0
set nextshellpath ""
set nextshelltype ""
puts stderr "PLAT: $plat"
switch -glob -- $plat {
"msys" - "mingw*" {
set os "win32"
}
default {
set os $plat
}
}
foreach ln [split $scriptdata \n] {
if {[string trim $ln] eq ""} {continue}
if {!$in_data} {
@ -740,14 +840,14 @@ foreach ln [split $scriptdata \n] {
set in_data 1
}
} else {
if {[string match "*@SET*nextshellpath?${plat}_*" $ln]} {
if {[string match "*@SET*nextshellpath?${os}_*" $ln]} {
set lineparts [split $ln =]
set tail [lindex $lineparts 1]
set nextshellpath [string trimright $tail {_"}]
if {$nextshellpath ne "" && $nextshelltype ne ""} {
break
}
} elseif {[string match "*@SET*nextshelltype?${plat}_*" $ln]} {
} elseif {[string match "*@SET*nextshelltype?${os}_*" $ln]} {
set lineparts [split $ln =]
set tail [lindex $lineparts 1]
set nextshelltype [string trimright $tail {_"}]
@ -760,31 +860,112 @@ foreach ln [split $scriptdata \n] {
}
}
if {$nextshelltype ne "tcl" && $nextshelltype ne "none"} {
set script_rootname [file rootname $script_as_called]
if {$nextshelltype in "pwsh powershell"} {
set scrname [file rootname [info script]].ps1
set arglist [list]
foreach a $::argv {
set a "'$a'"
lappend arglist $a
# experimental
set script_ps1 $script_rootname.ps1
set arglist $::argv
if {[file extension $script_as_called] ne ".ps1"} {
#we need to ensure .ps1 is up to date
set needs_updating 0
if {![file exists $script_ps1]} {
set needs_updating 1
} else {
#both exist
if {[file size $script_as_called] != [file size $script_ps1]} {
set needs_updating 1
} else {
#both exist with same size - do full check that they're identical
catch {package require sha256}
if {[package provide sha256] ne ""} {
set h1 [sha2::sha256 -hex -file $script_as_called]
set h2 [sha2::sha256 -hex -file $script_ps1]
if {[string length $h1] != 64 || [string length $h2] != 64} {
set needs_updating 1
} elseif {$h1 ne $h2} {
set needs_updating 1
}
} else {
#manually compare - scripts aren't too big, so slurp and string compare is fine
set fd [open $script_as_called]
chan configure $fd -translation binary
set data1 [read $fd]
close $fd
set fd [open $script_ps1]
chan configure $fd -translation binary
set data2 [read $fd]
close $fd
if {![string equal $data1 $data2]} {
set needs_updating 1
}
}
}
}
if {$needs_updating} {
file copy -force $script_as_called $script_ps1
}
} else {
#when called on the .ps1 - we assume it's up to date - review
}
set scrname $script_ps1
#set arglist [list]
#foreach a $::argv {
# set a "'$a'"
# lappend arglist $a
#}
} else {
set scrname [info script]
set scrname $script_as_called
set arglist $::argv
}
puts stdout "tclsh launching subshell of type: $nextshelltype shellpath: $nextshellpath on script $scrname with args: $arglist"
#todo - handle /usr/bin/env
#todo - exitcode
if {[llength $nextshellpath] == 1 && [string index $nextshellpath 0] eq {"} && [string index $nextshellpath end] eq {"}} {
set nextshell_words [list $nextshellpath]
} else {
set nextshell_words $nextshellpath
#review - test spaced quoted words in nextshellpath?
#
#if {[llength $nextshellpath] == 1 && [string index $nextshellpath 0] eq {"} && [string index $nextshellpath end] eq {"}} {
# set nextshell_words [list $nextshellpath]
#} else {
# set nextshell_words $nextshellpath
#}
#perform any msys argument munging on a cmd/cmd.exe based nextshellpath before we convert the first word to an auto_exec path
switch -glob -- $plat {
"msys" - "mingw*" {
set cmdword [lindex $nextshellpath 0]
#we only act on cmd or cmd.exe - not a full path such as c:/WINDOWS/system32/cmd.exe
#the nextshellpath should generally be configured as cmd /c ... or cmd.exe ... but specifying it as a path could allow bypassing this un-munging.
#The un-munging only applies to msys/mingw, so such bypassing should be unnecessary - review
#maint: keep this munging in sync with zsh/bash and perl blocks which must also do msys mangling
if {[regexp {^cmd$|^cmd[.]exe$} $cmdword]} {
#need to deal with msys argument munging
puts stderr "cmd call via msys detected. performing translation of /c to //C"
#for now we only deal with /C or /c - todo - other cmd.exe flags?
#In this context we would usually only be using cmd.exe /c to launch older 'desktop' powershell to avoid spaced-argument problems - so we aren't expecting other flags
set new_nextshellpath [list $cmdword]
#for now - just do what zsh munging does - bash regex/string/array processing is tedious and footgunny for the unfamiliar (me),
#so determine the minimum viable case for code there, then port behaviour to perl/tcl msys munging sections.
foreach w [lrange $nextshellpath 1 end] {
if {[regexp {^/[Cc]$} $w]} {
lappend new_nextshellpath {//C}
} else {
lappend new_nextshellpath $w
}
}
set nextshellpath $new_nextshellpath
}
}
}
set ns_firstword [lindex $nextshellpath 0]
if {[string index $ns_firstword 0] eq {"} && [string index $ns_firstword end] eq {"}} {
set ns_firstword [string range $ns_firstword 1 end-1]
}
#review - is this test for extra layer of double quoting on first word really necessary?
#if we are treaing $nextshellpath as a tcl list - the first layer of double quotes will already have disappeared
##if {[string index $ns_firstword 0] eq {"} && [string index $ns_firstword end] eq {"}} {
## set ns_firstword [string range $ns_firstword 1 end-1]
##}
if {[string match {/*/env} $ns_firstword] && $::tcl_platform(platform) ne "windows"} {
if {$::tcl_platform(platform) ne "windows" && [string match {/*/env} $ns_firstword]} {
set exec_part $nextshellpath
} else {
set epath [auto_execok $ns_firstword]
@ -794,6 +975,10 @@ if {$nextshelltype ne "tcl" && $nextshelltype ne "none"} {
set exec_part [list {*}$epath {*}[lrange $nextshellpath 1 end]]
}
}
puts stdout "tclsh launching subshell of type: $nextshelltype shellpath: $nextshellpath on script $scrname with args: $arglist"
puts stdout "exec: $exec_part $scrname $arglist"
catch {exec {*}$exec_part $scrname {*}$arglist <@stdin >@stdout 2>@stderr} emsg eopts
if {[dict exists $eopts -errorcode]} {
@ -837,11 +1022,6 @@ namespace eval ::punk::multishell {
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin Tcl Payload
#puts "script : [info script]"
#puts "argcount : $::argc"
#puts "argvalues: $::argv"
#puts "argv0 : $::argv0"
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-payload>
puts stderr "No tcl code for this script. Try another program such as zsh or bash or perl"
@ -867,8 +1047,11 @@ if {[::punk::multishell::is_main]} {
HEREDOC1B_HIDE_FROM_BASH_AND_SH
# Be wary of any non-trivial sed/awk etc - can be brittle to maintain across linux,freebsd,macosx due to differing implementations \
echo "var0: $0 @: $@"
# use oldschool backticks and sed - lowest common denominator \
ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
# use oldschool backticks and sed (posix - lowest common denominator) \
# ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` \
# some ps impls will return arguments - so last field not always appropriate \
# some ps impls don't have -o (e.g cygwin) so ps_shellname may remain empty and emit an error \
ps_shellname=`ps -o pid,comm -p $$ | awk '$1 != "PID" {print $2}'`
# \
echo "shell from ps: $ps_shellname"
# \
@ -900,8 +1083,11 @@ pop() {
}
# ------------------------------------------------------------------------------
# non-bash-like posix diversion \
if [ "$ps_shellname" != "bash" ] && [ "$ps_shellname" != "zsh" ]; then
# non-bash-like posix diversion
# we don't use $BASH_VERSION/$ZSH_VERSION as these can still be set when for example
# sh is a symlink to bash (posix-mode bash - reduced bashism capabilities?)
# if our ps_shellname didn't contain a result, don't divert and risk looping
if [ -n "$ps_shellname" ] && [ "$ps_shellname" != "bash" ] && [ "$ps_shellname" != "zsh" ] ; then
shift
pop $#
eval "$POP_EXPR"
@ -919,17 +1105,14 @@ if false==false # else {
then
: #
# zsh/bash \
shift && set -- "${@:1:$((${#@}-1))}"
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- sh/bash script section
# -- leave as is if all that is required is launching the Tcl payload"
# --
# -- 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.
# -- zsh/bash script section
# --
# -- review - for zsh do we want to use: setopt KSH_ARRAYS ?
# -- arrays in bash 0-based vs 1-based in zsh
# -- stick to the @:i:len syntax which is same for both
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
plat=$(uname -s) #platform/system
@ -952,18 +1135,31 @@ elif [[ "$plat" == "MINGW64"* ]]; then
elif [[ "$plat" == "CYGWIN_NT"* ]]; then
os="win32"
elif [[ "$plat" == "MSYS_NT"* ]]; then
#review..
echo MSYS
#win32 binaries - but e.g tclsh installed in msys reports ::tcl_platform(platform) as 'unix'
#review..
#Need to consider the difference between when msys2 was launched (which strips some paths and sets up the environment)
# vs if the msys2 sh was called - (actually bash) in which case paths will be different
#wsl and cygwin or msys2 can commonly be problematic combinations - primarily due to path issues
#e.g "c:/windows/system32/" is quite likely in the path ahead of msys,git etc.
#e.g It means a /usr/bin/env bash call may launch the (linux elf) bash for wsl rather than the msys bash
#
#msys provides win32 binaries - but e.g tclsh installed in msys reports ::tcl_platform(platform) as 'unix'
#bash reports $OSTYPE msys
#there are statements around the web that cmd /c .. will work under msys2
# - but from experience, it can be required to use cmd //c ...
# or MSYS2_ARG_CONV_ECL='*' cmd /c ..
# This seems to be because process arguments that look like unix paths are converted to windows paths :/
#review!
os="win32"
#review - need ps/sed/awk to determine shell?
interp = `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
interp=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
#use 'command -v' (shell builtin preferred over external which)
shellpath=`command -v $interp`
shellfolder="${shellpath%/*}" #avoid dependency on basename or dirname
#"c:/windows/system32/" is quite likely in the path ahead of msys,git etc.
#This breaks calls to various unix utils such as sed etc (wsl related?)
export PATH="$shellfolder${PATH:+:${PATH}}"
elif [[ "$OSTYPE" == "win32" ]]; then
os="win32"
@ -985,6 +1181,8 @@ elif [[ "$ps_shellname" == "zsh" ]]; then
else
#fallback - doesn't seem to work in zsh - untested in early bash
IFS=$'\n' arr_oslines=($shellconfiglines)
IFS=$' \t\n'
# review
fi
nextshellpath=""
nextshelltype=""
@ -1008,8 +1206,56 @@ exitcode=0
#-- sh/bash launches nextscript here instead of shebang line at top
if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then
echo zsh/bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
#e.g /usr/bin/env tclsh "$0" "$@"
${nextshellpath} "$0" "$@"
script="$0"
if [[ "$nextshelltype" == "pwsh" || "$nextshelltype" == "powershell" ]]; then
#powershell requires the file extension to be .ps1 (always - on windows)
#on other platforms it's not required if a shebang line is used - but this script must be shebangless for portability and to maintain polyglot capabilities.
cmdpattern="[.]cmd$"
if [[ "$script" =~ $cmdpattern ]]; then
ps1script="${script%????}.ps1"
if ! cmp -s "$script" "$ps1script" ; then
#ps1script either different or missing
#on windows - batch script copies .cmd -> .ps1 if not identical
cp -f "$script" "$ps1script"
fi
script=$ps1script
fi
fi
if [[ "$plat" == "MSYS_NT"* ]]; then
#we need to deal with MSYS argument munging
cmdpattern="^cmd.exe |^cmd "
#do not double quote cmdpattern - or it will be treated as literal string
if [[ "$nextshellpath" =~ $cmdpattern ]]; then
#for now - tell the user what's going on
echo "cmd call via msys detected. performing translation of /c to //c and escaping backslashes in script path" >&2
#flags to cmd.exe such as /c are interpreted by msys as looking like a unix path
#review - for nextshellpath targets specified in the block for win32 - we don't expect unix paths (?)
#what about other flags? - can we just double up all forward slashes?
#maint: keep this munging in sync with the tcl block and perl block which must also do msys munging
nextshellpath="${nextshellpath// \/[cC] / \/\/c }"
# echo "new nextshellpath: ${nextshellpath}"
#review -
#don't double quote this
script=${script//\\/\\\\}
fi
echo "calling ${nextshellpath} $script $@"
#load into array
cmd_array=($nextshellpath)
cmd_array+=("$script") #add script, which may contain spaces as a single entry ?
cmd_array+=( "$@" ) #add each element of args to array as a separate entry (equiv ? "${arr[@]}")
# printf "%s\n" "${cmd_array[@]}"
"${cmd_array[@]}"
# this works to make nextshellpath run - but joins $@ members incorrectly
#eval ${nextshellpath} "$script" "$@"
else
#e.g /usr/bin/env tclsh "$0" "$@"
${nextshellpath} "$script" "$@"
fi
exitcode=$?
#echo "zsh/bash reporting exitcode: ${exitcode}"
@ -1189,18 +1435,44 @@ if ($match.Success) {
$admininfo = $match.Groups[1].Value
$asadmin = $admininfo.Contains("asadmin=1")
if ($asadmin) {
if ($args[0] -eq "PUNK-ELEVATED") {
# May be present if launch and elevation was done via cmd.exe script
# shift away first arg
$newargs = $args | Select-Object -Skip 1
} else {
$newargs = $args
}
# -Wait e.g for starting a service or other operations which remainder of script may depend on
$arguments = @("-NoProfile","-NoLogo", "-NoExit", "-ExecutionPolicy", "Bypass")
$arguments += @("-File", $($MyInvocation.MyCommand.Path))
foreach ($a in $newargs) {
if ($a -match '\s') {
$arguments += "`"$a`""
} else {
$arguments += $a
}
}
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
# If not elevated, relaunch with elevated privileges
# -Wait e.g for starting a service or other operations which remainder of script may depend on
$arguments = @("-NoProfile", "-NoExit", "-ExecutionPolicy", "Bypass")
$arguments += @("-File", $($MyInvocation.MyCommand.Path))
$arguments += $args
Write-Host "Powershell elevating using start-process with -Verb RunAs"
if ($PSVersionTable.PSEdition -eq 'Core') {
Start-Process -FilePath "pwsh.exe" -ArgumentList $arguments -Wait -Verb RunAs
} else {
Start-Process -FilePath "powershell.exe" -ArgumentList $arguments -Wait -Verb RunAs
}
Exit # Exit the current non-elevated process
} else {
if ($args[0] -eq "PUNK-ELEVATED") {
#Already elevated (by cmd.exe)
#.. but it is impossible to modify or reassign the automatic $args variable
# so let's start yet another whole new process just to remove one leading argument so the custom script can operate on parameters cleanly - thanks powershell :/
if ($PSVersionTable.PSEdition -eq 'Core') {
Start-Process -FilePath "pwsh.exe" -ArgumentList $arguments -NoNewWindow -Wait
} else {
Start-Process -FilePath "powershell.exe" -ArgumentList $arguments -NoNewWindow -Wait
}
Exit
}
}
}
}

2718
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/flagfilter-0.3.1.tm

File diff suppressed because it is too large Load Diff

5
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/char-0.1.0.tm

@ -186,8 +186,9 @@ tcl::namespace::eval punk::char {
set r [list "" {*}[split $lowbits ""] $ridx {*}$charlist]
$t add_row $r
}
puts stderr $t
$t print
set result [$t print]
$t destroy
return $result
}
#just the 7-bit ascii. use [page ascii] for the 8-bit layout

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

@ -916,19 +916,19 @@ namespace eval punk::mix::commandset::scriptwrap {
return $configd
}
proc _get_nextshell_script {configd} {
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________________________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________"
#delimeters
@ -941,7 +941,7 @@ namespace eval punk::mix::commandset::scriptwrap {
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 n [expr {128 - [string length $path]}]
set _path [string repeat _ $n]
set type [dict get $v nextshelltype]
set n [expr {16 - [string length $type]}]

2
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm

@ -2418,6 +2418,8 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
#if {[lindex $command 0] eq "runx"} {}
#temporary hack.
#todo - use happy path return options for non-primary result (like www package) ?
if {
[string equal -length [string length "d/ "] "d/ " $commandstr] || \
[string equal "d/\n" $commandstr] || \

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

@ -283,9 +283,47 @@ tcl::namespace::eval punk::zip {
#if there is an external preamble - extract that. (if there is also an internal preamble - ignore and consider part of the archive-data)
#Otherwise extract an internal preamble.
#if neither -
#if neither -?
#review - reconsider auto-determination of internal vs external preamble
proc extract_preamble {infile outfile_preamble {outfile_zip ""}} {
punk::args::define {
@id -id ::punk::zip::extract_preamble
@cmd -name punk::zip::extract_preamble -help\
"Split a zipfs based executable or library into its constituent
binary and zip parts.
Note that the binary preamble might be either 'within' the zip offsets,
or simply catenated prior to an unadjusted zip.
Some build processes may have 'adjusted' the zip offsets to make the zip cover the entire file
('file based' offset) whilst the more modern approach is to simply concatenate the binary and the zip
('archive based' offset). An archive-based offset is simpler and more reliably points to the proper
split location. It also allows 'zipfs info //zipfs:/app' to return the correct offset information.
Either way, extract_preamble can usually separate them, but in the unusual case that there is both an
external preamble and a preamble within the zip, only the external preamble will be split, with the
internal one remaining in the zip.
The inverse of this process would be to extract the .zip file created by this split to a folder,
e.g extracted_zip_folder (adjusting contents as required) and then to run:
zipfs mkimg newbinaryname.exe extracted_zip_folder <prefix> \"\" <extracted_preamble_or_alternative exe>
"
@values -min 2 -max 3
infile -type file -optional 0 -help\
"Name of existing tcl executable or shared lib with attached zipfs filesystem"
outfile_preamble -optional 0 -type file -help\
"Name of output file for binary preamble to be extracted to.
If this file already exists, an error will be raised"
outfile_zip -default "" -type file -help\
"Name of output file for zip data to be extracted to.
If this file already exists, an error will be raised"
}
proc extract_preamble {args} {
set argd [punk::args::parse $args withid ::punk::zip::extract_preamble]
lassign [dict values $argd] leaders opts values received
set infile [dict get $values infile]
set outfile_preamble [dict get $values outfile_preamble]
set outfile_zip [dict get $values outfile_zip]
set inzip [open $infile r]
fconfigure $inzip -encoding iso8859-1 -translation binary
if {[file exists $outfile_preamble]} {
@ -346,7 +384,7 @@ tcl::namespace::eval punk::zip {
#we could scan from top (ugly) - and with binary prefixes we could get false positives in the data that look like PK\3\4 headers
#we could either work out the format for all possible executables that could be appended (across all platforms) and understand where they end?
#or we just look for the topmost PK\3\4 header pointed to by a CDR record - and assume the CDR is complete
#step one - read all the CD records and find the highest pointed to local file record (which isn't necessarily the first - but should get us above most if not all of the zip data)
#we can't assume they're ordered in any particular way - so we in theory have to look at them all.
set baseoffset "unknown"

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

@ -427,7 +427,7 @@ namespace eval shellrun {
cmdarg -type any -multiple 1 -optional 1
}]
proc runerr {args} {
set argd [punk::args::parse $args withid ::shellrun::runout]
set argd [punk::args::parse $args withid ::shellrun::runerr]
lassign [dict values $argd] leaders opts values received
if {[dict exists $received "-nonewline"]} {

2718
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/flagfilter-0.3.1.tm

File diff suppressed because it is too large Load Diff

5
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/char-0.1.0.tm

@ -186,8 +186,9 @@ tcl::namespace::eval punk::char {
set r [list "" {*}[split $lowbits ""] $ridx {*}$charlist]
$t add_row $r
}
puts stderr $t
$t print
set result [$t print]
$t destroy
return $result
}
#just the 7-bit ascii. use [page ascii] for the 8-bit layout

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

@ -916,19 +916,19 @@ namespace eval punk::mix::commandset::scriptwrap {
return $configd
}
proc _get_nextshell_script {configd} {
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________________________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________"
#delimeters
@ -941,7 +941,7 @@ namespace eval punk::mix::commandset::scriptwrap {
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 n [expr {128 - [string length $path]}]
set _path [string repeat _ $n]
set type [dict get $v nextshelltype]
set n [expr {16 - [string length $type]}]

2
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm

@ -2418,6 +2418,8 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
#if {[lindex $command 0] eq "runx"} {}
#temporary hack.
#todo - use happy path return options for non-primary result (like www package) ?
if {
[string equal -length [string length "d/ "] "d/ " $commandstr] || \
[string equal "d/\n" $commandstr] || \

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

@ -283,9 +283,47 @@ tcl::namespace::eval punk::zip {
#if there is an external preamble - extract that. (if there is also an internal preamble - ignore and consider part of the archive-data)
#Otherwise extract an internal preamble.
#if neither -
#if neither -?
#review - reconsider auto-determination of internal vs external preamble
proc extract_preamble {infile outfile_preamble {outfile_zip ""}} {
punk::args::define {
@id -id ::punk::zip::extract_preamble
@cmd -name punk::zip::extract_preamble -help\
"Split a zipfs based executable or library into its constituent
binary and zip parts.
Note that the binary preamble might be either 'within' the zip offsets,
or simply catenated prior to an unadjusted zip.
Some build processes may have 'adjusted' the zip offsets to make the zip cover the entire file
('file based' offset) whilst the more modern approach is to simply concatenate the binary and the zip
('archive based' offset). An archive-based offset is simpler and more reliably points to the proper
split location. It also allows 'zipfs info //zipfs:/app' to return the correct offset information.
Either way, extract_preamble can usually separate them, but in the unusual case that there is both an
external preamble and a preamble within the zip, only the external preamble will be split, with the
internal one remaining in the zip.
The inverse of this process would be to extract the .zip file created by this split to a folder,
e.g extracted_zip_folder (adjusting contents as required) and then to run:
zipfs mkimg newbinaryname.exe extracted_zip_folder <prefix> \"\" <extracted_preamble_or_alternative exe>
"
@values -min 2 -max 3
infile -type file -optional 0 -help\
"Name of existing tcl executable or shared lib with attached zipfs filesystem"
outfile_preamble -optional 0 -type file -help\
"Name of output file for binary preamble to be extracted to.
If this file already exists, an error will be raised"
outfile_zip -default "" -type file -help\
"Name of output file for zip data to be extracted to.
If this file already exists, an error will be raised"
}
proc extract_preamble {args} {
set argd [punk::args::parse $args withid ::punk::zip::extract_preamble]
lassign [dict values $argd] leaders opts values received
set infile [dict get $values infile]
set outfile_preamble [dict get $values outfile_preamble]
set outfile_zip [dict get $values outfile_zip]
set inzip [open $infile r]
fconfigure $inzip -encoding iso8859-1 -translation binary
if {[file exists $outfile_preamble]} {
@ -346,7 +384,7 @@ tcl::namespace::eval punk::zip {
#we could scan from top (ugly) - and with binary prefixes we could get false positives in the data that look like PK\3\4 headers
#we could either work out the format for all possible executables that could be appended (across all platforms) and understand where they end?
#or we just look for the topmost PK\3\4 header pointed to by a CDR record - and assume the CDR is complete
#step one - read all the CD records and find the highest pointed to local file record (which isn't necessarily the first - but should get us above most if not all of the zip data)
#we can't assume they're ordered in any particular way - so we in theory have to look at them all.
set baseoffset "unknown"

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

@ -427,7 +427,7 @@ namespace eval shellrun {
cmdarg -type any -multiple 1 -optional 1
}]
proc runerr {args} {
set argd [punk::args::parse $args withid ::shellrun::runout]
set argd [punk::args::parse $args withid ::shellrun::runerr]
lassign [dict values $argd] leaders opts values received
if {[dict exists $received "-nonewline"]} {

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

@ -916,19 +916,19 @@ namespace eval punk::mix::commandset::scriptwrap {
return $configd
}
proc _get_nextshell_script {configd} {
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
#@SET "nextshellpath[win32___________]=tclsh___________________________________________________________________________________________________________________________"
#@SET "nextshelltype[win32___________]=tcl_____________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[dragonflybsd____]=tcl_____________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[freebsd_________]=tcl_____________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[netbsd__________]=tcl_____________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[linux___________]=tcl_____________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[macosx__________]=tcl_____________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
#@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
#@SET "nextshelltype[other___________]=tcl_____________"
#delimeters
@ -941,7 +941,7 @@ namespace eval punk::mix::commandset::scriptwrap {
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 n [expr {128 - [string length $path]}]
set _path [string repeat _ $n]
set type [dict get $v nextshelltype]
set n [expr {16 - [string length $type]}]

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

@ -95,19 +95,19 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
@REM If more than 64 chars needed for a target, it can still be done but overall script padding may need checking/adjusting
@REM Supporting more explicit oses than those listed may also require script padding adjustment
: <<nextshell_start>>
@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
@SET "nextshellpath[win32___________]=tclsh___________________________________________________________________________________________________________________________"
@SET "nextshelltype[win32___________]=tcl_____________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
@SET "nextshelltype[dragonflybsd____]=tcl_____________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
@SET "nextshelltype[freebsd_________]=tcl_____________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
@SET "nextshelltype[netbsd__________]=tcl_____________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
@SET "nextshelltype[linux___________]=tcl_____________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
@SET "nextshelltype[macosx__________]=tcl_____________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________________________________________________________________________"
@SET "nextshelltype[other___________]=tcl_____________"
: <<nextshell_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).
@ -119,7 +119,7 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
@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 :stringTrimTrailingUnderscores "%selected_shellpath%" selected_shellpath_trimmed
@CALL SET "keyRemoved=%%validshelltypes:!selected_shelltype!=%%"
@REM @ECHO keyremoved %keyRemoved%
@REM Note that 'powershell' e.g v5 is just a fallback for when pwsh is not available
@ -151,6 +151,7 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
@SET "winpath=%~dp0" %= e.g c:\punkshell\bin\ %=
@SET "fname=%~nx0"
@SET "scriptrootname=%~dp0%~n0" %= e.g c:\punkshell\bin\runtime (full path without extension) unavailable after shift, so store it =%
@SET "fullscriptname=%~dp0%~n0%~x0"
@REM @ECHO fname %fname%
@REM @ECHO winpath %winpath%
@REM @ECHO commandlineascalled %0
@ -162,6 +163,62 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
@CALL :stringToUpper %~nx0 capscripttail
@REM @ECHO capscriptname: %capscripttail%
@goto skip_parameter_wrangling
@set argCount=30
@rem This is the max number of args we are willing to handle. also bounded by approx 8k char limit of cmd.exe
@rem We do not loop over %* to count args as it is brittle for some inputs e.g will always skip cmd.exe separators e.g comma and semicolon
@rem Set argCount higher if desired, but there is a small amount of additional looping overhead.
@set tmpfile_base=%TEMP%\punkbatch_params
@call :getUniqueFile %tmpfile_base% ".txt" paramfile
@echo %paramfile%
%= NOTE when we loop like this using the percent-n args and shift, we lose unquoted separators such as comma and semicolon %=
@rem https://stackoverflow.com/questions/26551/how-can-i-pass-arguments-to-a-batch-file/5493124#5493124
@rem outer loop required to redirect all rem lines at once to file
@for %%x in (1) do @(
@for /L %%f in (1,1,%argCount%) do @(
@set "argnum=%%~nf"
@set "a1=%%1"
@rem @set "argname=%%!argnum!"
@rem @echo argname: !argname!
@call :rem_output !argnum! !a1!
@shift
)
) > %paramfile%
@echo off
@set "newcommandline= "
@(set target=cmd_pwsh)
@if "%target%"=="cmd_pwsh" (
@for /F "delims=" %%L in (%paramfile%) do @(
SETLOCAL DisableDelayedExpansion
set "param=%%L"
@REM @echo ######### %%L
@rem call :buildcmdline newcommandline param "{" "}"
@rem call :buildcmdline newcommandline param ' ' %= cmd.exe /c powershell ... -c %=
call :buildcmdline newcommandline param %= cmd.exe /c powershell ... -f %=
@rem @echo .
)
) ELSE (
@for /F "delims=" %%L in (%paramfile%) do @(
SETLOCAL DisableDelayedExpansion
set "param=%%L"
call :buildcmdline newcommandline param
)
)
@REM padding
SETLOCAL EnableDelayedExpansion
@echo off
@IF EXIST %paramfile% (
@DEL /F /Q %paramfile%
)
@IF EXIST %paramfile% (
echo failed to delete %paramfile%
cat %paramfile%
)
:skip_parameter_wrangling
@IF "%nftail%"=="%capscripttail%" (
@ECHO forcing asadmin=1 due to file name on filesystem being uppercase
@SET "asadmin=1"
@ -189,31 +246,112 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
: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 pre = "/c %fullscriptname% PUNK-ELEVATED " >> "%vbsGetPrivileges%"
@REM @echo pre = "PUNK-ELEVATED " >> "%vbsGetPrivileges%"
@echo args = pre >> "%vbsGetPrivileges%"
@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO args = args ^& Chr(34) ^& strArg ^& Chr(34) ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script in new window due to administrator elevation
@GOTO skiptest
%= Option Explicit =%
%= We need a child process to locate the current script. =%
@ECHO Const FLAG_PROCESS = "winver.exe" >> "%vbsGetPrivileges%"
%= ' WMI constants %=
@ECHO Const wbemFlagForwardOnly = 32 >> "%vbsGetPrivileges%"
%=' Generate a unique value to be used as a flag =%
@ECHO Dim guid >> "%vbsGetPrivileges%
@ECHO guid = Left(CreateObject("Scriptlet.TypeLib").GUID,38) >> "%vbsGetPrivileges%"
%= ' Start a process using the indicated flag inside its command line =%
@ECHO WScript.CreateObject("WScript.Shell").Run """" ^& FLAG_PROCESS ^& """ " ^& guid, 0, False >> "%vbsGetPrivileges%"
%= ' To retrieve process information a WMI reference is needed =%
@ECHO Dim wmi >> "%vbsGetPrivileges%"
@ECHO Set wmi = GetObject("winmgmts:{impersonationLevel=impersonate}^!\\.\root\cimv2") >> "%vbsGetPrivileges%"
%= ' Query the list of processes with the flag in its command line, retrieve the =%
%= ' process ID of its parent process ( our script! ) and terminate the process =%
@ECHO Dim colProcess, process, myProcessID >> "%vbsGetPrivileges%"
@ECHO Set colProcess = wmi.ExecQuery( _>> "%vbsGetPrivileges%"
@ECHO "SELECT ParentProcessID From Win32_Process " ^& _>> "%vbsGetPrivileges%"
@ECHO "WHERE Name='" ^& FLAG_PROCESS ^& "' " ^& _>> "%vbsGetPrivileges%"
@ECHO "AND CommandLine LIKE '%%" ^& guid ^& "%%'" _>> "%vbsGetPrivileges%"
@ECHO ,"WQL" , wbemFlagForwardOnly _>> "%vbsGetPrivileges%"
@ECHO ) >> "%vbsGetPrivileges%"
@ECHO For Each process In colProcess >> "%vbsGetPrivileges%"
@ECHO myProcessID = process.ParentProcessID >> "%vbsGetPrivileges%"
@ECHO process.Terminate >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%"
%= ' Knowing the process id of our script we can query the process list =%
%= ' and retrieve its command line =%
@ECHO Dim commandLine >> "%vbsGetPrivileges%"
@ECHO set colProcess = wmi.ExecQuery( _>> "%vbsGetPrivileges%"
@ECHO "SELECT CommandLine From Win32_Process " ^& _>> "%vbsGetPrivileges%"
@ECHO "WHERE ProcessID=" ^& myProcessID _>> "%vbsGetPrivileges%"
@ECHO ,"WQL" , wbemFlagForwardOnly _>> "%vbsGetPrivileges%"
@ECHO ) >> "%vbsGetPrivileges%"
@ECHO For Each process In colProcess >> "%vbsGetPrivileges%"
@ECHO commandLine = process.CommandLine >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%"
@ECHO WScript.Echo "raw commandline: " ^& commandLine >>"%vbsGetPrivileges%"
%= ' Done =%
@ECHO intpos = 0 >> "%vbsGetPrivileges%"
@ECHO intCount = 0 >> "%vbsGetPrivileges%"
@ECHO intstartsearch = 1 >> "%vbsGetPrivileges%"
@ECHO intmax = 100 >> "%vbsGetPrivileges%"
@ECHO do While intCount ^< 4 and intmax ^> 0 >> "%vbsGetPrivileges%"
@ECHO intpos = InStr(intstartsearch, commandline, """") >> "%vbsGetPrivileges%"
@ECHO if intpos ^<^> 0 then >> "%vbsGetPrivileges%"
@ECHO intCount = intCount + 1 >> "%vbsGetPrivileges%"
@ECHO if intcount = 4 then >> "%vbsGetPrivileges%"
@ECHO ' wscript.echo "position: " ^& intpos >> "%vbsGetPrivileges%"
@ECHO commandline = Mid(commandline,intpos+1) >> "%vbsGetPrivileges%"
@ECHO exit do >> "%vbsGetPrivileges%"
@ECHO else >> "%vbsGetPrivileges%"
@ECHO intstartsearch = intpos + 1 >> "%vbsGetPrivileges%"
@ECHO end if >> "%vbsGetPrivileges%"
@ECHO end if >> "%vbsGetPrivileges%"
@ECHO intmax = intmax -1 >> "%vbsGetPrivileges%"
@ECHO Loop >> "%vbsGetPrivileges%"
@ECHO if intcount ^< 4 then >> "%vbsGetPrivileges%"
@ECHO err.raise vbObjectError + 1001, "vbsGetPrivileges", "failed to parse commandline" >> "%vbsGetPrivileges%"
@ECHO end if >> "%vbsGetPrivileges%"
@ECHO commandline = pre ^& commandline >> "%vbsGetPrivileges%"
@ECHO WScript.Echo "commandline: " ^& commandLine >>"%vbsGetPrivileges%"
@ECHO WScript.Echo "args: " ^& args >>"%vbsGetPrivileges%"
:skiptest
@ECHO UAC.ShellExecute "cmd.exe", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@REM @ECHO UAC.ShellExecute "%fullscriptname%", commandline, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script "%fullscriptname%" in new window due to administrator elevation with args: "%*"
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@REM @"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" !newcommandline!
@EXIT /B
@REM buffer
@REM buffer
:gotPrivileges
@REM setlocal & pushd .
@PUSHD .
@cd /d %~dp0
@cd /d %winpath%
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
@DEL "%vbsGetPrivileges%" 1>nul 2>nul
@SET arglist=%arglist:~14%
@SHIFT
)
: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" (
@if not exist "%scriptrootname%.ps1" (
@SET need_ps1=1
) ELSE (
fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different
fc "%fullscriptname%" "%scriptrootname%.ps1" >nul || goto different
@REM @ECHO "files same"
@SET need_ps1=0
)
@ -223,74 +361,13 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
@SET need_ps1=1
:pscontinue
@IF !need_ps1!==1 (
COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL
COPY "%fullscriptname%" "%scriptrootname%.ps1" >NUL
)
@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "!selected_shelltype_trimmed!"=="none" (
SET selected_shelltype_trimmed=pwsh
)
@set argCount=30
@rem This is the max number of args we are willing to handle. also bounded by approx 8k char limit of cmd.exe
@rem We do not loop over %* to count args as it is brittle for some inputs e.g will always skip cmd.exe separators e.g comma and semicolon
@rem Set argCount higher if desired, but there is a small amount of additional looping overhead.
@set tmpfile_base=%TEMP%\punkbatch_params
@call :getUniqueFile %tmpfile_base% ".txt" paramfile
@echo %paramfile%
%= NOTE when we loop like this using the percent-n args, we lose unquoted separators such as comma and semicolon %=
@rem https://stackoverflow.com/questions/26551/how-can-i-pass-arguments-to-a-batch-file/5493124#5493124
@rem outer loop required to redirect all rem lines at once to file
@for %%x in (1) do @(
@for /L %%f in (1,1,%argCount%) do @(
@set "argnum=%%~nf"
@set "a1=%%1"
@rem @set "argname=%%!argnum!"
@rem @echo argname: !argname!
@call :rem_output !argnum! !a1!
@shift
)
) > %paramfile%
@echo off
@set "newcommandline= "
@(set target=cmd_pwsh)
@if "%target%"=="cmd_pwsh" (
@for /F "delims=" %%L in (%paramfile%) do @(
SETLOCAL DisableDelayedExpansion
set "param=%%L"
@REM @echo ######### %%L
@rem call :buildcmdline newcommandline param "{" "}"
@rem call :buildcmdline newcommandline param ' ' %= cmd.exe /c powershell ... -c %=
call :buildcmdline newcommandline param %= cmd.exe /c powershell ... -f %=
@rem @echo .
)
) ELSE (
@for /F "delims=" %%L in (%paramfile%) do @(
SETLOCAL DisableDelayedExpansion
set "param=%%L"
call :buildcmdline newcommandline param
)
)
@REM padding
SETLOCAL EnableDelayedExpansion
@echo off
@IF EXIST %paramfile% (
@DEL /F /Q %paramfile%
)
@IF EXIST %paramfile% (
echo failed to delete %paramfile%
cat %paramfile%
)
@REM @SET "squoted_args="
@REM @for %%a in (%*) do @(
@REM set "v=%%a"
@ -311,19 +388,24 @@ SETLOCAL EnableDelayedExpansion
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
@rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%scriptrootname%.ps1" %arglist%
@rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted
cmd /c pwsh -nop -nol -ExecutionPolicy bypass -f "%scriptrootname%.ps1" !newcommandline!
@rem pwsh -nop -nologo -ExecutionPolicy bypass -f "%scriptrootname%.ps1" %arglist% %= ok =%
@rem cmd /c pwsh -nop -nologo -ExecutionPolicy bypass -f "%scriptrootname%.ps1" !newcommandline!
!selected_shellpath_trimmed! "%scriptrootname%.ps1" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
REM TODO prompt user with option to call script to install pwsh using winget
@rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
cmd /c powershell -nop -nol -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline!
%= powershell with -file flag treats it's arguments differently to pwsh - we need cmd /c to preserve args with spaces =%
cmd /c powershell -nop -nologo -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" %arglist%
@rem cmd /c powershell -nop -nologo -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "!selected_shelltype_trimmed!"=="powershell" (
@rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
cmd /c powershell -nop -nol -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline!
%= powershell with -file flag treats it's arguments differently to pwsh - we need cmd /c to preserve args with spaces =%
@rem @echo powershell - !selected_shellpath_trimmed! "%scriptrootname%.ps1" %arglist%
@rem cmd /c powershell -nop -nologo -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" %arglist% %= ok - this works =%
!selected_shellpath_trimmed! "%scriptrootname%.ps1" %arglist%
@rem cmd /c powershell -nop -nologo -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
) ELSE (
IF "!selected_shelltype_trimmed!"=="wslbash" (
@ -337,24 +419,23 @@ SETLOCAL EnableDelayedExpansion
REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode
@ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!"
%selected_shellpath_trimmed% "%winpath%%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
@REM @ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!"
!selected_shellpath_trimmed! "%winpath%%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
) ELSE (
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
SET task_exitcode=66
@REM boundary padding
GOTO :exit_multishell
)
)
)
)
@REM batch file library functions
@GOTO :endlib
@REM padding
@REM padding
@REM padding
@REM padding
%= ---------------------------------------------------------------------- =%
@rem courtesy of dbenham
@ -458,6 +539,7 @@ do if not defined param1 set %%~"param1=%2%%~"
rem %1 #%2#
@exit /b
@rem padding
@REM courtesy of: https://stackoverflow.com/users/463115/jeb
:strlen stringVar returnVar
@(
@ -506,6 +588,8 @@ do if not defined param1 set %%~"param1=%2%%~"
)
@EXIT /B
@REM padding
@REM padding
: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
@ -545,6 +629,7 @@ do if not defined param1 set %%~"param1=%2%%~"
@EXIT /B
@REM boundary padding
@REM boundary padding
@REM boundary padding
:getNormalizedScriptTail
@SETLOCAL
@SET "result=%~nx0"
@ -654,14 +739,14 @@ do if not defined param1 set %%~"param1=%2%%~"
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
:stringTrimTrailingUnderscores
@SETLOCAL
@SET "rtrn=%~2"
@SET "string=%~1"
@SET "trimstring=%~1"
@REM trim up to 63 underscores from the end of a string using string substitution
@REM trim up to 127 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:________###=###%"
@ -707,10 +792,11 @@ do if not defined param1 set %%~"param1=%2%%~"
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- 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
# -- It is tuned to run (and possibly divert to different payload shell) when called from cmd.exe as a batch file, tclsh,sh,zsh,bash,perl or pwsh/powershell script
# -- i.e it is a polyglot file.
# -- The payload target (by os) is defined in the nextshell block at the top which is constructed when generating the polyglot
# -- using the tcl 'dev scriptwrap.multishell' command in a tcl punk shell
# -- The payload can be tcl,perl,powershell/pwsh or zsh/bash.
# -- The specific layout including some lines that appear just as comments is quite sensitive to change.
# -- It can be called on unix or windows platforms with or without the interpreter being specified on the commandline.
# -- e.g ./scriptname.cmd in sh or zsh or bash
@ -720,7 +806,13 @@ do if not defined param1 set %%~"param1=%2%%~"
rename set ""; rename S set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@
#---------------------------------------------------------------------
puts "info script : [info script]"
#puts "argcount : $::argc"
#puts "argvalues: $::argv"
#puts "argv0 : $::argv0"
# -- --- --- --- --- --- --- --- --- --- --- ---
#divert to configured nextshell
set script_as_called [info script]
package require platform
set plat_full [platform::generic]
set plat [lindex [split $plat_full -] 0]
@ -733,6 +825,14 @@ set in_data 0
set nextshellpath ""
set nextshelltype ""
puts stderr "PLAT: $plat"
switch -glob -- $plat {
"msys" - "mingw*" {
set os "win32"
}
default {
set os $plat
}
}
foreach ln [split $scriptdata \n] {
if {[string trim $ln] eq ""} {continue}
if {!$in_data} {
@ -740,14 +840,14 @@ foreach ln [split $scriptdata \n] {
set in_data 1
}
} else {
if {[string match "*@SET*nextshellpath?${plat}_*" $ln]} {
if {[string match "*@SET*nextshellpath?${os}_*" $ln]} {
set lineparts [split $ln =]
set tail [lindex $lineparts 1]
set nextshellpath [string trimright $tail {_"}]
if {$nextshellpath ne "" && $nextshelltype ne ""} {
break
}
} elseif {[string match "*@SET*nextshelltype?${plat}_*" $ln]} {
} elseif {[string match "*@SET*nextshelltype?${os}_*" $ln]} {
set lineparts [split $ln =]
set tail [lindex $lineparts 1]
set nextshelltype [string trimright $tail {_"}]
@ -760,31 +860,111 @@ foreach ln [split $scriptdata \n] {
}
}
if {$nextshelltype ne "tcl" && $nextshelltype ne "none"} {
set script_rootname [file rootname $script_as_called]
if {$nextshelltype in "pwsh powershell"} {
set scrname [file rootname [info script]].ps1
set arglist [list]
foreach a $::argv {
set a "'$a'"
lappend arglist $a
# experimental
set script_ps1 $script_rootname.ps1
set arglist $::argv
if {[file extension $script_as_called] ne ".ps1"} {
#we need to ensure .ps1 is up to date
set needs_updating 0
if {![file exists $script_ps1]} {
set needs_updating 1
} else {
#both exist
if {[file size $script_as_called] != [file size $script_ps1]} {
set needs_updating 1
} else {
#both exist with same size - do full check that they're identical
catch {package require sha256}
if {[package provide sha256] ne ""} {
set h1 [sha2::sha256 -hex -file $script_as_called]
set h2 [sha2::sha256 -hex -file $script_ps1]
if {[string length $h1] != 64 || [string length $h2] != 64} {
set needs_updating 1
} elseif {$h1 ne $h2} {
set needs_updating 1
}
} else {
#manually compare - scripts aren't too big, so slurp and string compare is fine
set fd [open $script_as_called]
chan configure $fd -translation binary
set data1 [read $fd]
close $fd
set fd [open $script_ps1]
chan configure $fd -translation binary
set data2 [read $fd]
close $fd
if {![string equal $data1 $data2]} {
set needs_updating 1
}
}
}
}
if {$needs_updating} {
file copy -force $script_as_called $script_ps1
}
} else {
#when called on the .ps1 - we assume it's up to date - review
}
set scrname $script_ps1
#set arglist [list]
#foreach a $::argv {
# set a "'$a'"
# lappend arglist $a
#}
} else {
set scrname [info script]
set scrname $script_as_called
set arglist $::argv
}
puts stdout "tclsh launching subshell of type: $nextshelltype shellpath: $nextshellpath on script $scrname with args: $arglist"
#todo - handle /usr/bin/env
#todo - exitcode
if {[llength $nextshellpath] == 1 && [string index $nextshellpath 0] eq {"} && [string index $nextshellpath end] eq {"}} {
set nextshell_words [list $nextshellpath]
} else {
set nextshell_words $nextshellpath
#review - test spaced quoted words in nextshellpath?
#
#if {[llength $nextshellpath] == 1 && [string index $nextshellpath 0] eq {"} && [string index $nextshellpath end] eq {"}} {
# set nextshell_words [list $nextshellpath]
#} else {
# set nextshell_words $nextshellpath
#}
#perform any msys argument munging on a cmd/cmd.exe based nextshellpath before we convert the first word to an auto_exec path
switch -glob -- $plat {
"msys" - "mingw*" {
set cmdword [lindex $nextshellpath 0]
#we only act on cmd or cmd.exe - not a full path such as c:/WINDOWS/system32/cmd.exe
#the nextshellpath should generally be configured as cmd /c ... or cmd.exe ... but specifying it as a path could allow bypassing this un-munging.
#The un-munging only applies to msys/mingw, so such bypassing should be unnecessary - review
#maint: keep this munging in sync with zsh/bash and perl blocks which must also do msys mangling
if {[regexp {^cmd$|^cmd[.]exe$} $cmdword]} {
#need to deal with msys argument munging
#for now we only deal with /C or /c - todo - other cmd.exe flags?
#In this context we would usually only be using cmd.exe /c to launch older 'desktop' powershell to avoid spaced-argument problems - so we aren't expecting other flags
set new_nextshellpath [list $cmdword]
#for now - just do what zsh munging does - bash regex/string/array processing is tedious and footgunny for the unfamiliar (me),
#so determine the minimum viable case for code there, then port behaviour to perl/tcl msys munging sections.
foreach w [lrange $nextshellpath 1 end] {
if {[regexp {^/[Cc]$} $w]} {
lappend new_nextshellpath {//C}
} else {
lappend new_nextshellpath $w
}
}
set nextshellpath $new_nextshellpath
}
}
}
set ns_firstword [lindex $nextshellpath 0]
if {[string index $ns_firstword 0] eq {"} && [string index $ns_firstword end] eq {"}} {
set ns_firstword [string range $ns_firstword 1 end-1]
}
#review - is this test for extra layer of double quoting on first word really necessary?
#if we are treaing $nextshellpath as a tcl list - the first layer of double quotes will already have disappeared
##if {[string index $ns_firstword 0] eq {"} && [string index $ns_firstword end] eq {"}} {
## set ns_firstword [string range $ns_firstword 1 end-1]
##}
if {[string match {/*/env} $ns_firstword] && $::tcl_platform(platform) ne "windows"} {
if {$::tcl_platform(platform) ne "windows" && [string match {/*/env} $ns_firstword]} {
set exec_part $nextshellpath
} else {
set epath [auto_execok $ns_firstword]
@ -794,6 +974,10 @@ if {$nextshelltype ne "tcl" && $nextshelltype ne "none"} {
set exec_part [list {*}$epath {*}[lrange $nextshellpath 1 end]]
}
}
puts stdout "tclsh launching subshell of type: $nextshelltype shellpath: $nextshellpath on script $scrname with args: $arglist"
puts stdout "exec: $exec_part $scrname $arglist"
catch {exec {*}$exec_part $scrname {*}$arglist <@stdin >@stdout 2>@stderr} emsg eopts
if {[dict exists $eopts -errorcode]} {
@ -837,11 +1021,6 @@ namespace eval ::punk::multishell {
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin Tcl Payload
#puts "script : [info script]"
#puts "argcount : $::argc"
#puts "argvalues: $::argv"
#puts "argv0 : $::argv0"
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-payload>
puts stderr "No tcl code for this script. Try another program such as zsh or bash or perl"
@ -867,8 +1046,11 @@ if {[::punk::multishell::is_main]} {
HEREDOC1B_HIDE_FROM_BASH_AND_SH
# Be wary of any non-trivial sed/awk etc - can be brittle to maintain across linux,freebsd,macosx due to differing implementations \
echo "var0: $0 @: $@"
# use oldschool backticks and sed - lowest common denominator \
ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
# use oldschool backticks and sed (posix - lowest common denominator) \
# ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` \
# some ps impls will return arguments - so last field not always appropriate \
# some ps impls don't have -o (e.g cygwin) so ps_shellname may remain empty and emit an error \
ps_shellname=`ps -o pid,comm -p $$ | awk '$1 != "PID" {print $2}'`
# \
echo "shell from ps: $ps_shellname"
# \
@ -900,8 +1082,11 @@ pop() {
}
# ------------------------------------------------------------------------------
# non-bash-like posix diversion \
if [ "$ps_shellname" != "bash" ] && [ "$ps_shellname" != "zsh" ]; then
# non-bash-like posix diversion
# we don't use $BASH_VERSION/$ZSH_VERSION as these can still be set when for example
# sh is a symlink to bash (posix-mode bash - reduced bashism capabilities?)
# if our ps_shellname didn't contain a result, don't divert and risk looping
if [ -n "$ps_shellname" ] && [ "$ps_shellname" != "bash" ] && [ "$ps_shellname" != "zsh" ] ; then
shift
pop $#
eval "$POP_EXPR"
@ -919,17 +1104,14 @@ if false==false # else {
then
: #
# zsh/bash \
shift && set -- "${@:1:$((${#@}-1))}"
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- sh/bash script section
# -- leave as is if all that is required is launching the Tcl payload"
# --
# -- 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.
# -- zsh/bash script section
# --
# -- review - for zsh do we want to use: setopt KSH_ARRAYS ?
# -- arrays in bash 0-based vs 1-based in zsh
# -- stick to the @:i:len syntax which is same for both
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
plat=$(uname -s) #platform/system
@ -952,18 +1134,31 @@ elif [[ "$plat" == "MINGW64"* ]]; then
elif [[ "$plat" == "CYGWIN_NT"* ]]; then
os="win32"
elif [[ "$plat" == "MSYS_NT"* ]]; then
#review..
echo MSYS
#win32 binaries - but e.g tclsh installed in msys reports ::tcl_platform(platform) as 'unix'
#review..
#Need to consider the difference between when msys2 was launched (which strips some paths and sets up the environment)
# vs if the msys2 sh was called - (actually bash) in which case paths will be different
#wsl and cygwin or msys2 can commonly be problematic combinations - primarily due to path issues
#e.g "c:/windows/system32/" is quite likely in the path ahead of msys,git etc.
#e.g It means a /usr/bin/env bash call may launch the (linux elf) bash for wsl rather than the msys bash
#
#msys provides win32 binaries - but e.g tclsh installed in msys reports ::tcl_platform(platform) as 'unix'
#bash reports $OSTYPE msys
#there are statements around the web that cmd /c .. will work under msys2
# - but from experience, it can be required to use cmd //c ...
# or MSYS2_ARG_CONV_ECL='*' cmd /c ..
# This seems to be because process arguments that look like unix paths are converted to windows paths :/
#review!
os="win32"
#review - need ps/sed/awk to determine shell?
interp = `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
interp=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
#use 'command -v' (shell builtin preferred over external which)
shellpath=`command -v $interp`
shellfolder="${shellpath%/*}" #avoid dependency on basename or dirname
#"c:/windows/system32/" is quite likely in the path ahead of msys,git etc.
#This breaks calls to various unix utils such as sed etc (wsl related?)
export PATH="$shellfolder${PATH:+:${PATH}}"
elif [[ "$OSTYPE" == "win32" ]]; then
os="win32"
@ -985,6 +1180,8 @@ elif [[ "$ps_shellname" == "zsh" ]]; then
else
#fallback - doesn't seem to work in zsh - untested in early bash
IFS=$'\n' arr_oslines=($shellconfiglines)
IFS=$' \t\n'
# review
fi
nextshellpath=""
nextshelltype=""
@ -1008,8 +1205,55 @@ exitcode=0
#-- sh/bash launches nextscript here instead of shebang line at top
if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then
echo zsh/bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
#e.g /usr/bin/env tclsh "$0" "$@"
${nextshellpath} "$0" "$@"
script="$0"
if [[ "$nextshelltype" == "pwsh" || "$nextshelltype" == "powershell" ]]; then
#powershell requires the file extension to be .ps1 (always - on windows)
#on other platforms it's not required if a shebang line is used - but this script must be shebangless for portability and to maintain polyglot capabilities.
cmdpattern="[.]cmd$"
if [[ "$script" =~ $cmdpattern ]]; then
ps1script="${script%????}.ps1"
if ! cmp -s "$script" "$ps1script" ; then
#ps1script either different or missing
#on windows - batch script copies .cmd -> .ps1 if not identical
cp -f "$script" "$ps1script"
fi
script=$ps1script
fi
fi
if [[ "$plat" == "MSYS_NT"* ]]; then
#we need to deal with MSYS argument munging
cmdpattern="^cmd.exe |^cmd "
#do not double quote cmdpattern - or it will be treated as literal string
if [[ "$nextshellpath" =~ $cmdpattern ]]; then
#for now - tell the user what's going on
echo "cmd call via msys detected. performing translation of /c to //c and escaping backslashes in script path"
#flags to cmd.exe such as /c are interpreted by msys as looking like a unix path
#review - for nextshellpath targets specified in the block for win32 - we don't expect unix paths (?)
#what about other flags? - can we just double up all forward slashes?
#maint: keep this munging in sync with the tcl block and perl block which must also do msys munging
nextshellpath="${nextshellpath// \/[cC] / \/\/c }"
# echo "new nextshellpath: ${nextshellpath}"
#don't double quote this
script=${script//\\/\\\\}
fi
echo "calling ${nextshellpath} $script $@"
#load into array
cmd_array=($nextshellpath)
cmd_array+=("$script") #add script, which may contain spaces as a single entry ?
cmd_array+=( "$@" ) #add each element of args to array as a separate entry (equiv ? "${arr[@]}")
# printf "%s\n" "${cmd_array[@]}"
"${cmd_array[@]}"
# this works to make nextshellpath run - but joins $@ members incorrectly
#eval ${nextshellpath} "$script" "$@"
else
#e.g /usr/bin/env tclsh "$0" "$@"
${nextshellpath} "$script" "$@"
fi
exitcode=$?
#echo "zsh/bash reporting exitcode: ${exitcode}"
@ -1189,18 +1433,44 @@ if ($match.Success) {
$admininfo = $match.Groups[1].Value
$asadmin = $admininfo.Contains("asadmin=1")
if ($asadmin) {
if ($args[0] -eq "PUNK-ELEVATED") {
# May be present if launch and elevation was done via cmd.exe script
# shift away first arg
$newargs = $args | Select-Object -Skip 1
} else {
$newargs = $args
}
# -Wait e.g for starting a service or other operations which remainder of script may depend on
$arguments = @("-NoProfile","-NoLogo", "-NoExit", "-ExecutionPolicy", "Bypass")
$arguments += @("-File", $($MyInvocation.MyCommand.Path))
foreach ($a in $newargs) {
if ($a -match '\s') {
$arguments += "`"$a`""
} else {
$arguments += $a
}
}
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
# If not elevated, relaunch with elevated privileges
# -Wait e.g for starting a service or other operations which remainder of script may depend on
$arguments = @("-NoProfile", "-NoExit", "-ExecutionPolicy", "Bypass")
$arguments += @("-File", $($MyInvocation.MyCommand.Path))
$arguments += $args
Write-Host "Powershell elevating using start-process with -Verb RunAs"
if ($PSVersionTable.PSEdition -eq 'Core') {
Start-Process -FilePath "pwsh.exe" -ArgumentList $arguments -Wait -Verb RunAs
} else {
Start-Process -FilePath "powershell.exe" -ArgumentList $arguments -Wait -Verb RunAs
}
Exit # Exit the current non-elevated process
} else {
if ($args[0] -eq "PUNK-ELEVATED") {
#Already elevated (by cmd.exe)
#.. but it is impossible to modify or reassign the automatic $args variable
# so let's start yet another whole new process just to remove one leading argument so the custom script can operate on parameters cleanly - thanks powershell :/
if ($PSVersionTable.PSEdition -eq 'Core') {
Start-Process -FilePath "pwsh.exe" -ArgumentList $arguments -NoNewWindow -Wait
} else {
Start-Process -FilePath "powershell.exe" -ArgumentList $arguments -NoNewWindow -Wait
}
Exit
}
}
}
}

Loading…
Cancel
Save