From c9085f33f5256ba52b563b2a4531bf9b14550453 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Sun, 24 Aug 2025 15:18:47 +1000 Subject: [PATCH] scriptwrapper work --- bin/getzig.cmd | 1077 +++++++++++++++++ src/modules/punk-0.1.tm | 44 +- .../mix/commandset/scriptwrap-999999.0a1.0.tm | 29 +- .../utility/scriptappwrappers/multishell.cmd | 58 +- src/modules/punk/ubl-999999.0a1.0.tm | 289 +++++ src/modules/punk/ubl-buildversion.txt | 3 + src/scriptapps/getzig.bash | 15 + src/scriptapps/getzig.ps1 | 186 +++ src/scriptapps/getzig_wrap.toml | 21 + 9 files changed, 1678 insertions(+), 44 deletions(-) create mode 100644 bin/getzig.cmd create mode 100644 src/modules/punk/ubl-999999.0a1.0.tm create mode 100644 src/modules/punk/ubl-buildversion.txt create mode 100644 src/scriptapps/getzig.bash create mode 100644 src/scriptapps/getzig.ps1 create mode 100644 src/scriptapps/getzig_wrap.toml diff --git a/bin/getzig.cmd b/bin/getzig.cmd new file mode 100644 index 00000000..5584fa66 --- /dev/null +++ b/bin/getzig.cmd @@ -0,0 +1,1077 @@ +: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^ +set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @' +: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \ +: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \ +: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" + +: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH' +: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT - except for first double quoted section. +: shebang line is not required on unix or windows and will reduce functionality and/or portability. +: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care. +@GOTO :skip_perl_pod_start ^; +=begin excludeperl +: skip_perl_pod_start +: Continuation char at end of this line and rem with curly-braces used to exlude Tcl from the whole cmd block \ +: { +@REM ############################################################################################################################ +@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, (some sh) and/or powershelll (powershell.exe or pwsh.exe) +@REM It should remain portable between unix-like OSes & windows if the proper structure is maintained. +@REM ############################################################################################################################ +@REM Change the value of nextshell to one of the supported types, and add code within payload sections for tcl,sh,bash,powershell as appropriate. +@REM This wrapper can be edited manually (carefully!) - or bash,tcl,perl,powershell scripts can be wrapped using the Tcl-based punkshell system +@REM e.g from within a running punkshell: dev scriptwrap.multishell -outputfolder +@REM Call with sh, bash, perl, or tclsh. (powershell untested on unix) +@REM Due to lack of shebang (#! line) Unix-like systems will hopefully default to a flavour of sh that can divert to bash if the script is called without an interpreter - but it may depend on the shell in use when called. +@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only. +@REM in batch scripts - array syntax with square brackets is a simulation of arrays or associative arrays. +@REM note that many shells linked as sh do not support substition syntax and may fail - e.g dash etc - generally bash should be used in this context +@SETLOCAL EnableExtensions EnableDelayedExpansion +@SET "validshelltypes= pwsh____________ powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________ none____________" +@REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch +@REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx +@REM The horrible underscore-padded fixed-widths are to keep the batch labels aligned whilst allowing values to be set +@REM If more than 64 chars needed for a target, it can still be done but overall script padding may need checking/adjusting +@REM Supporting more explicit oses than those listed may also require script padding adjustment +: <> +@SET "nextshellpath[win32___________]=pwsh____________________________________________________________" +@SET "nextshelltype[win32___________]=pwsh____________" +@SET "nextshellpath[dragonflybsd____]=/usr/bin/env bash_______________________________________________" +@SET "nextshelltype[dragonflybsd____]=bash____________" +@SET "nextshellpath[freebsd_________]=/usr/bin/env bash_______________________________________________" +@SET "nextshelltype[freebsd_________]=bash____________" +@SET "nextshellpath[netbsd__________]=/usr/bin/env bash_______________________________________________" +@SET "nextshelltype[netbsd__________]=bash____________" +@SET "nextshellpath[linux___________]=/usr/bin/env bash_______________________________________________" +@SET "nextshelltype[linux___________]=bash____________" +@SET "nextshellpath[macosx__________]=/usr/bin/env bash_______________________________________________" +@SET "nextshelltype[macosx__________]=bash____________" +@SET "nextshellpath[other___________]=/usr/bin/env bash_______________________________________________" +@SET "nextshelltype[other___________]=bash____________" +: <> +@rem asadmin is for automatic elevation to administrator. Separate window will be created (seems unavoidable with current elevation mechanism) and user will still get security prompt (probably reasonable). +: <> +@SET "asadmin=0" +: <> +@REM @ECHO nextshelltype is %nextshelltype[win32___________]% +@REM @SET "selected_shelltype=%nextshelltype[win32___________]%" +@SET "selected_shelltype=%nextshelltype[win32___________]%" +@REM @ECHO selected_shelltype %selected_shelltype% +@CALL :stringTrimTrailingUnderscores %selected_shelltype% selected_shelltype_trimmed +@REM @ECHO selected_shelltype_trimmed %selected_shelltype_trimmed% +@SET "selected_shellpath=%nextshellpath[win32___________]%" +@CALL :stringTrimTrailingUnderscores %selected_shellpath% selected_shellpath_trimmed +@CALL SET "keyRemoved=%%validshelltypes:!selected_shelltype!=%%" +@REM @ECHO keyremoved %keyRemoved% +@REM Note that 'powershell' e.g v5 is just a fallback for when pwsh is not available +@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ### +@REM -- cmd/batch file section (ignored on unix but should be left in place) +@REM -- This section intended mainly to launch the next shell (and to escalate privileges if necessary) +@REM -- Avoid customising this if you are not familiar with batch scripting. cmd/batch script can be useful, but is probably the least expressive language and most error prone. +@REM -- For example - as this file needs to use unix-style lf line-endings - the label scanner is susceptible to the 512Byte boundary issue: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888 +@REM -- This label issue can be triggered/abused in files with crlf line endings too - but it is less likely to happen accidentaly. +@REm -- See also: https://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/4095133#4095133 +@REM ############################################################################################################################ +@REM -- Due to this issue -seemingly trivial edits of the batch file section can break the script! (for Windows anyway) +@REM -- Even something as simple as adding or removing an @REM +@REM -- From within punkshell - use: +@REM -- deck scriptwrap.checkfile +@REM -- to check your templates or final wrapped scripts for byte boundary issues +@REM -- It will report any labels that are on boundaries +@REM -- This is why the nextshell value above is a 2 digit key instead of a string - so that editing the value doesn't change the byte offsets. +@REM -- Editing your sh,bash,tcl,pwsh payloads is much less likely to cause an issue. There is the possibility of the final batch :exit_multishell label spanning a boundary - so testing using deck scriptwrap.checkfile is still recommended. +@REM -- Alternatively, as you should do anyway - test the final script on windows +@REM -- Aside from adding comments/whitespace to tweak the location of labels - you can try duplicating the label (e.g just add the label on a line above) but this is not guaranteed to work in all situations. +@REM -- '@REM' is a safer comment mechanism than a leading colon - which is used sparingly here. +@REM -- A colon anywhere in the script that happens to land on a 512 Byte boundary (from file start or from a callsite) could be misinterpreted as a label +@REM -- It is unknown what versions of cmd interpreters behave this way - and deck scriptwrap.checkfile doesn't check all such boundaries. +@REm -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided +@REM ############################################################################################################################ +@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections +@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ### +@SET "winpath=%~dp0" +@SET "fname=%~nx0" +@REM @ECHO fname %fname% +@REM @ECHO winpath %winpath% +@REM @ECHO commandlineascalled %0 +@REM @ECHO commandlineresolved %~f0 +@CALL :getNormalizedScriptTail nftail +@REM @ECHO normalizedscripttail %nftail% +@CALL :getFileTail %0 clinetail +@REM @ECHO clinetail %clinetail% +@CALL :stringToUpper %~nx0 capscripttail +@REM @ECHO capscriptname: %capscripttail% + +@IF "%nftail%"=="%capscripttail%" ( + @ECHO forcing asadmin=1 due to file name on filesystem being uppercase + @SET "asadmin=1" +) else ( + @CALL :stringToUpper %clinetail% capcmdlinetail + @REM @ECHO capcmdlinetail !capcmdlinetail! + IF "%clinetail%"=="!capcmdlinetail!" ( + @ECHO forcing asadmin=1 due to cmdline scriptname in uppercase + @set "asadmin=1" + ) +) +@SET "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs" +@SET arglist=%* +@SET "qstrippedargs=args%arglist%" +@SET "qstrippedargs=%qstrippedargs:"=%" +@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" ( + GOTO :gotPrivileges +) +@IF !asadmin!==1 ( + net file 1>NUL 2>NUL + @IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges ) +) +@REM padding +@REM padding +@REM padding +@REM padding +@REM padding +@REM padding +@REM padding +@REM padding +@REM padding +@REM padding +@REM padding +@REM padding +@GOTO skip_privileges +:getPrivileges +@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges ) +@ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%" +@ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%" +@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%" +@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%" +@ECHO Next >> "%vbsGetPrivileges%" +@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%" +@ECHO Launching script in new window due to administrator elevation +@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %* +@EXIT /B + +:gotPrivileges +@REM setlocal & pushd . +@PUSHD . +@cd /d %~dp0 +@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" ( + @DEL "%vbsGetPrivileges%" 1>nul 2>nul + @SET arglist=%arglist:~14% +) + +:skip_privileges +@SET need_ps1=0 +@REM we want the ps1 to exist even if the nextshell isn't powershell +@if not exist "%~dp0%~n0.ps1" ( + @SET need_ps1=1 +) ELSE ( + fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different + @REM @ECHO "files same" + @SET need_ps1=0 +) +@GOTO :pscontinue +:different +@REM @ECHO "files differ" +@SET need_ps1=1 +:pscontinue +@IF !need_ps1!==1 ( + COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL +) +@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /? +@IF "!selected_shelltype_trimmed!"=="none" ( + SET selected_shelltype_trimmed=pwsh +) +@IF "!selected_shelltype_trimmed!"=="pwsh" ( + REM pwsh vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time + REM test availability of preferred option of powershell7+ pwsh + pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL + SET pwshtest_exitcode=!errorlevel! + REM ECHO pwshtest_exitcode !pwshtest_exitcode! + REM fallback to powershell if pwsh failed + IF !pwshtest_exitcode!==0 ( + pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% + SET task_exitcode=!errorlevel! + ) ELSE ( + REM TODO prompt user with option to call script to install pwsh using winget + REM powershell -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% + powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %arglist% + SET task_exitcode=!errorlevel! + ) +) ELSE ( + IF "!selected_shelltype_trimmed!"=="powershell" ( + powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %arglist% + SET task_exitcode=!errorlevel! + ) ELSE ( + IF "!selected_shelltype_trimmed!"=="wslbash" ( + CALL :getWslPath %winpath% wslpath + REM ECHO wslfullpath "!wslpath!%fname%" + %selected_shellpath_trimmed% "!wslpath!%fname%" %arglist% + SET task_exitcode=!errorlevel! + ) ELSE ( + REM perl or tcl or sh or bash + IF NOT "x%keyRemoved%"=="x%validshelltypes%" ( + REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl + REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx + REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode + @ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!" + %selected_shellpath_trimmed% "%~dp0%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 +@REM boundary padding +@GOTO :endlib + +:getWslPath +@SETLOCAL + @SET "_path=%~p1" + @SET "name=%~nx1" + @SET "drive=%~d1" + @SET "rtrn=%~2" + @REM Although drive letters on windows are normally upper case wslbash seems to expect lower case drive letters + @CALL :stringToLower %drive ldrive + @SET "result=/mnt/%ldrive:~0,1%%_path:\=/%%name%" +@ENDLOCAL & ( + @if "%~2" neq "" ( + SET "%rtrn%=%result%" + ) ELSE ( + ECHO %result% + ) +) +@EXIT /B + +:getFileTail +@REM return tail of file without any normalization e.g c:/punkshell/bin/Punk.cmd returns Punk.cmd even if file is punk.cmd +@REM we can't use things such as %~nx1 as it can change capitalisation +@REM This function is designed explicitly to preserve capitalisation +@REM accepts full paths with either / or \ as delimiters - or +@SETLOCAL + @SET "rtrn=%~2" + @SET "arg=%~1" + @REM @SET "result=%_arg:*/=%" + @REM @SET "result=%~1" + @SET LF=^ + + + : The above 2 empty lines are important. Don't remove + @CALL :stringContains "!arg!" "\" hasBackSlash + @IF "!hasBackslash!"=="true" ( + @for %%A in ("!LF!") do @( + @FOR /F %%B in ("!arg:\=%%~A!") do @set "result=%%B" + ) + ) ELSE ( + @CALL :stringContains "!arg!" "/" hasForwardSlash + @IF "!hasForwardSlash!"=="true" ( + @FOR %%A in ("!LF!") do @( + @FOR /F %%B in ("!arg:/=%%~A!") do @set "result=%%B" + ) + ) ELSE ( + @set "result=%arg%" + ) + ) +@ENDLOCAL & ( + @if "%~2" neq "" ( + @SET "%rtrn%=%result%" + ) ELSE ( + @ECHO %result% + ) +) +@EXIT /B +@REM boundary padding +@REM boundary padding +:getNormalizedScriptTail +@SETLOCAL + @SET "result=%~nx0" + @SET "rtrn=%~1" +@ENDLOCAL & ( + @IF "%~1" neq "" ( + @SET "%rtrn%=%result%" + ) ELSE ( + @ECHO %result% + ) +) +@EXIT /B + +:getNormalizedFileTailFromPath +@REM warn via echo, and do not set return variable if path not found +@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized' +@REM boundary padding +@REM boundary padding +@REM boundary padding +@REM boundary padding +@SETLOCAL + @CALL :stringContains %~1 "\" hasBackSlash + @CALL :stringContains %~1 "/" hasForwardSlash + @IF "%hasBackslash%-%hasForwardslash%"=="false-false" ( + @SET "P=%cd%%~1" + @CALL :getNormalizedFileTailFromPath "!P!" ftail2 + @SET "result=!ftail2!" + ) else ( + @IF EXIST "%~1" ( + @SET "result=%~nx1" + ) else ( + @ECHO error getNormalizedFileTailFromPath file not found: %~1 + @EXIT /B 1 + ) + ) + @SET "rtrn=%~2" +@ENDLOCAL & ( + @IF "%~2" neq "" ( + SET "%rtrn%=%result%" + ) ELSE ( + @ECHO getNormalizedFileTailFromPath %1 result: %result% + ) +) +@EXIT /B + +:stringContains +@REM usage: @CALL:stringContains string needle returnvarname +@SETLOCAL + @SET "rtrn=%~3" + @SET "string=%~1" + @SET "needle=%~2" + @IF "!string:%needle%=!"=="!string!" @( + @SET "result=false" + ) ELSE ( + @SET "result=true" + ) +@ENDLOCAL & ( + @IF "%~3" neq "" ( + @SET "%rtrn%=%result%" + ) ELSE ( + @ECHO stringContains %string% %needle% result: %result% + ) +) +@EXIT /B +@REM boundary padding +@REM boundary padding +:stringToUpper +@SETLOCAL + @SET "rtrn=%~2" + @SET "string=%~1" + @SET "capstring=%~1" + @FOR %%A in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO @( + @SET "capstring=!capstring:%%A=%%A!" + ) + @SET "result=!capstring!" +@ENDLOCAL & ( + @IF "%~2" neq "" ( + @SET "%rtrn%=%result%" + ) ELSE ( + @ECHO stringToUpper %string% result: %result% + ) +) +@EXIT /B +:stringToLower +@SETLOCAL + @SET "rtrn=%~2" + @SET "string=%~1" + @SET "retstring=%~1" + @FOR %%A in (a b c d e f g h i j k l m n o p q r s t u v w x y z) DO @( + @SET "retstring=!retstring:%%A=%%A!" + ) + @SET "result=!retstring!" +@ENDLOCAL & ( + @IF "%~2" neq "" ( + @SET "%rtrn%=%result%" + ) ELSE ( + @ECHO stringToLower %string% result: %result% + ) +) +@EXIT /B +@REM boundary padding +@REM boundary padding +:stringTrimTrailingUnderscores +@SETLOCAL + @SET "rtrn=%~2" + @SET "string=%~1" + @SET "trimstring=%~1" + @REM trim up to 63 underscores from the end of a string using string substitution + @SET "trimstring=%trimstring%###" + @SET "trimstring=%trimstring:________________________________###=###%" + @SET "trimstring=%trimstring:________________###=###%" + @SET "trimstring=%trimstring:________###=###%" + @SET "trimstring=%trimstring:____###=###%" + @SET "trimstring=%trimstring:__###=###%" + @SET "trimstring=%trimstring:_###=###%" + @SET "trimstring=%trimstring:###=%" + @SET "result=!trimstring!" +@ENDLOCAL & ( + @IF "%~2" neq "" ( + @SET "%rtrn%=%result%" + ) ELSE ( + @ECHO stringTrimTrailingUnderscores %string% result: %result% + ) +) +@EXIT /B +:isNumeric +@SETLOCAL + @SET "notnumeric="&FOR /F "delims=0123456789" %%i in ("%1") do set "notnumeric=%%i" + @IF defined notnumeric ( + @SET "result=false" + ) else ( + @SET "result=true" + ) + @SET "rtrn=%~2" +@ENDLOCAL & ( + @IF "%~2" neq "" ( + @SET "%rtrn%=%result%" + ) ELSE ( + @ECHO %result% + ) +) +@EXIT /B + +:endlib +: \ +@REM padding +@REM padding +@REM @SET taskexit_code=!errorlevel! & goto :exit_multishell +@GOTO :exit_multishell +# } +# -*- tcl -*- +# ## ### ### ### ### ### ### ### ### ### ### ### ### ### +# -- tcl script section +# -- This is a punk multishell file +# -- Primary payload target is Tcl, with sh,bash,powershell as helpers +# -- but it may equally be used with any of these being the primary script. +# -- It is tuned to run when called as a batch file, a tcl script a sh/bash script or a pwsh/powershell script +# -- i.e it is a polyglot file. +# -- The specific layout including some lines that appear just as comments is quite sensitive to change. +# -- It can be called on unix or windows platforms with or without the interpreter being specified on the commandline. +# -- e.g ./filename.polypunk.cmd in sh or bash +# -- e.g tclsh filename.cmd +# -- +# ## ### ### ### ### ### ### ### ### ### ### ### ### ### +rename set ""; rename S set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore +Hide :exit_multishell;Hide {<#};Hide '@ +namespace eval ::punk::multishell { + set last_script_root [file dirname [file normalize ${::argv0}/__]] + set last_script [file dirname [file normalize [info script]/__]] + if {[info exists ::argv0] && + $last_script eq $last_script_root + } { + set ::punk::multishell::is_main($last_script) 1 ;#run as executable/script - likely desirable to launch application and return an exitcode + } else { + set ::punk::multishell::is_main($last_script) 0 ;#sourced - likely to be being used as a library - no launch, no exit. Can use return. + } + if {"::punk::multishell::is_main" ni [info commands ::punk::multishell::is_main]} { + proc ::punk::multishell::is_main {{script_name {}}} { + if {$script_name eq ""} { + set script_name [file dirname [file normalize [info script]/--]] + } + if {![info exists ::punk::multishell::is_main($script_name)]} { + #e.g a .dll or something else unanticipated + puts stderr "Warning punk::multishell didn't recognize info script result: $script_name - will treat as if sourced and return instead of exiting" + puts stderr "Info: script_root: [file dirname [file normalize ${::argv0}/__]]" + return 0 + } + return [set ::punk::multishell::is_main($script_name)] + } + } +} +# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin Tcl Payload +#puts "script : [info script]" +#puts "argcount : $::argc" +#puts "argvalues: $::argv" +#puts "argv0 : $::argv0" +# -- --- --- --- --- --- --- --- --- --- --- --- + +# +puts stderr "No tcl code for this script. Try another program such as perl or bash" +# + +# +# + +# +# + + +# +# + + +# -- --- --- --- --- --- --- --- --- --- --- --- +# -- Best practice is to always return or exit above, or just by leaving the below defaults in place. +# -- If the multishell script is modified to have Tcl below the Tcl Payload section, +# -- then Tcl bracket balancing needs to be carefully managed in the shell and powershell sections below. +# -- Only the # in front of the two relevant if statements below needs to be removed to enable Tcl below +# -- but the sh/bash 'then' and 'fi' would also need to be uncommented. +# -- This facility left in place for experiments on whether configuration payloads etc can be appended +# -- to tail of file - possibly binary with ctrl-z char - but utility is dependent on which other interpreters/shells +# -- can be made to ignore/cope with such data. +if {[::punk::multishell::is_main]} { + exit 0 +} else { + return +} +# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload +# end hide from unix shells \ +HEREDOC1B_HIDE_FROM_BASH_AND_SH +# csh/tcsh/sh/bash use oldschool backticks and sed lowest common denominator \ +echo "script: `echo $0 | sed 's/^-//'`" +# csh/tcsh/sh/bash use oldschool backticks and sed lowest common denominator \ +echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` +#csh/tcsh diversion \ +test "$argv[*]" != "[*]" && ( /usr/bin/env bash $argv[*]; exit ) +#other non-bash diversion \ +test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && /usr/bin/env bash $0 +#review \ +test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && exit +# sh/bash \ +shift && set -- "${@:1:$#-1}" + +#echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'` +#------------------------------------------------------ +# -- This if block only needed if Tcl didn't exit or return above. +if false==false # else { + then + : # +# ## ### ### ### ### ### ### ### ### ### ### ### ### ### +# -- sh/bash script section +# -- leave as is if all that is required is launching the Tcl payload" +# -- +# -- Note that sh/bash script isn't called when running a .bat/.cmd from cmd.exe on windows by default +# -- adjust the %nextshell% value above +# -- if sh/bash scripting needs to run on windows too. +# -- +# ## ### ### ### ### ### ### ### ### ### ### ### ### ### + +if [[ "$OSTYPE" == "linux"* ]]; then + os="linux" +elif [[ "$OSTYPE" == "darwin"* ]]; then + os="macosx" +elif [[ "$OSTYPE" == "freebsd"* ]]; then + os="freebsd" +elif [[ "$OSTYPE" == "dragonflybsd"* ]]; then + os="dragonflybsd" +elif [[ "$OSTYPE" == "netbsd"* ]]; then + os="netbsd" +elif [[ "$OSTYPE" == "win32" ]]; then + os="win32" +elif [[ "$OSTYPE" == "msys" ]]; then + echo MSYS + os="win32" + #review - need ps/sed/awk to determine shell? + interp = `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` + #use 'command -v' (shell builtin preferred over external which) + shellpath=`command -v $interp` + shellfolder="${shellpath%/*}" #avoid dependency on basename or dirname + #"c:/windows/system32/" is quite likely in the path ahead of msys,git etc. + #This breaks calls to various unix utils such as sed etc (wsl related?) + export PATH="$shellfolder${PATH:+:${PATH}}" +else + #os="$OSTYPE" + os="other" +fi +echo ostype: $OSTYPE +shellconfigline=$( sed -n "/: <>/{:a;n;/: <>/q;p;ba}" "$0" | grep $os) +#echo $shellconfigline; +if [[ $shellconfigline == *"nextshelltype"* ]]; then + echo "found config for os $os" + split1="${shellconfigline#*=}" #remove everything through the first '=' + #echo "split1: $split1" + pathraw="${split1%%\"*}" #take everything before the quote - use %% to get longest match + pathraw="${pathraw//\"/}" #remove quote + nextshellpath="${pathraw/%_*/}" #remove trailing underscores (% = must match at end) + #echo "nextshellpath: $nextshellpath" + split2="${split1#*=}" + #echo "split2: $split2" + split2="${split2//\"/}" + nextshelltype="${split2/%_*/}" + echo "nextshelltype: $nextshelltype" +else + echo "unable to find config for os $os" + echo "shellconfigline: $shellconfigline" + nextshellpath="" + nextshelltype="" +fi +exitcode=0 +#-- sh/bash launches nextscript here instead of shebang line at top +if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then + #echo bash launching subshell of type $nextshelltype $nextshellpath on "$0" + #/usr/bin/env tclsh "$0" "$@" + ${nextshellpath} "$0" "$@" + + exitcode=$? + #echo "sh/bash reporting exitcode: ${exitcode}" + exit $exitcode + #-- override exitcode example + #exit 66 +else + #already in bash - don't launch another process or we would loop + #echo "bash payload" + : +fi +# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload +#printf "start of bash or sh code" + +# +#mkdir -p ./zig + +#tarball="zig-x86_64-windows-0.15.1.zip" +#tarball="zig-x86_64-freebsd-0.15.1.tar.xz" +tarball="zig-x86_64-linux-0.15.1.tar.xz" + +automation_name="punkshell+julian@precisium.com.au_target_by_latency" +uristring="https://ziglang.org" +full_uristring="${uristring}/download/0.15.1/${tarball}?source=${automation_name}" +echo "Unimplemented: Download from ${full_uristring} and extract manually" +#wget $full_uristring -O ./zig/zig-linux-x86_64-0.10.1.tar.xz +#tar -xf ./zig/zig-linux-x86_64-0.10.1.tar.xz -C ./zig --strip-components=1 +#rm ./zig/zig-linux-x86_64-0.10.1.tar.xz +#echo "Zig installed." +#./zig/zig version + +# + +# +# + +# -- --- --- --- --- --- --- --- +# +#-- sh/bash launches Tcl here instead of shebang line at top +#-- use exec to use exitcode (if any) directly from the tcl script +#exec /usr/bin/env tclsh "$0" "$@" +#-- alternative - can run sh/bash script after the tcl call. +#/usr/bin/env tclsh "$0" "$@" +#exitcode=$? +#echo "sh/bash reporting tcl exitcode: ${exitcode}" +#-- override exitcode example +#exit 66 +# +# -- --- --- --- --- --- --- --- + +# +# + + +#printf "sh/bash done \n" +# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end sh Payload +#------------------------------------------------------ +fi +exit ${exitcode} +# ## ### ### ### ### ### ### ### ### ### ### ### ### ### +# -- Perl script section +# -- leave the script below as is, if all that is required is launching the Tcl payload" +# -- +# -- Note that perl script isn't called by default when simply running this script by name +# -- adjust the nextshell value at the top of the script to point to perl +# -- +# ## ### ### ### ### ### ### ### ### ### ### ### ### ### +=cut +#!/user/bin/perl +my $exit_code = 0; +use Cwd qw(abs_path); +my $scriptname = abs_path($0); +#print "perl $scriptname\n"; +my $os = "$^O"; +if ($os eq "MSWin32") { + $os = "win32"; +} elsif ($os eq "darwin") { + $os = "macosx"; +} +print "os $os\n"; +# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload +#use ExtUtils::Installed; +#my $installed = ExtUtils::Installed->new(); +#my @modules = $installed->modules(); +#print "Modules:\n"; +#foreach my $m (@modules) { +# print "$m\n"; +#} +# -- --- --- + + + +my $i =1; +foreach my $a(@ARGV) { + print "Arg # $i: $a\n"; +} + +# +print STDERR "No perl code for this script. Try another program such as tcl or bash"; +# + +# +# + + + +# -- --- --- --- --- --- --- --- +# +#$exit_code=system("tclsh", $scriptname, @ARGV); +#print "perl reporting tcl exitcode: $exit_code"; +# +# -- --- --- --- --- --- --- --- + +# +# + + +# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload +exit $exit_code; +__END__ + +# end hide sh/bash/perl block from Tcl +# This comment with closing brace should stay in place whether if commented or not } +#------------------------------------------------------ +# begin hide powershell-block from Tcl - only needed if Tcl didn't exit or return above +if 0 { +: end heredoc1 - end hide from powershell \ +'@ +# ## ### ### ### ### ### ### ### ### ### ### ### ### ### +# -- powershell/pwsh section +# -- Do not edit if current file is the .ps1 +# -- Edit the corresponding .cmd and it will autocopy +# -- unbalanced braces { } here *even in comments* will cause problems if there was no Tcl exit or return above +# -- custom script should generally go below the begin_powershell_payload line +# ## ### ### ### ### ### ### ### ### ### ### ### ### ### +function GetScriptName { $myInvocation.ScriptName } +$scriptname = GetScriptName +function GetDynamicParamDictionary { + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$true, Mandatory=$true)] + [string] $CommandName + ) + + begin { + # Get a list of params that should be ignored (they're common to all advanced functions) + $CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) | + Get-Member -MemberType Properties | + Select-Object -ExpandProperty Name + } + + process { + # Create the dictionary that this scriptblock will return: + $DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary + + # Convert to object array and get rid of Common params: + (Get-Command $CommandName | select -exp Parameters).GetEnumerator() | + Where-Object { $CommonParameterNames -notcontains $_.Key } | + ForEach-Object { + $DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter ( + $_.Key, + $_.Value.ParameterType, + $_.Value.Attributes + ) + $DynParamDictionary.Add($_.Key, $DynamicParameter) + } + + # Return the dynamic parameters + return $DynParamDictionary + } +} +# GetDynamicParamDictionary +# - This can make it easier to share a single set of param definitions between functions +# - sample usage +#function ParameterDefinitions { +# param( +# [Parameter(Mandatory)][string] $myargument +# ) +#} +#function psmain { +# [CmdletBinding()] +# param() +# dynamicparam { GetDynamicParamDictionary ParameterDefinitions } +# process { +# #called once with $PSBoundParameters dictionary +# #can be used to validate arguments, or set a simpler variable name for access +# switch ($PSBoundParameters.keys) { +# 'myargumentname' { +# Set-Variable -Name $_ -Value $PSBoundParameters."$_" +# } +# #... +# } +# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) { +# #... +# } +# } +# end { +# #Main function logic +# Write-Host "myargumentname value is: $myargumentname" +# #myotherfunction @PSBoundParameters +# } +#} +#psmain @args +#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host +#"Script Name : {0}" -f $scriptname | write-host +#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host +#"powershell args : {0}" -f ($args -join ", ") | write-host +# -- --- --- --- +$startTag = ": <>" +$endTag = ": <>" +$fileContent = Get-Content $scriptname -Raw +$pattern = "(?s)$startTag(.*?)$endTag" +$matches = [regex]::Matches($fileContent,$pattern) +$admininfo = $matches[0].Groups[1].Value +$asadmin = 0 +if ($matches.count) { + $asadmin = $admininfo.Contains("asadmin=1") + if ($asadmin) { + if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + # If not elevated, relaunch with elevated privileges + # -Wait e.g for starting a service or other operations which remainder of script may depend on + $arguments = @("-NoProfile", "-NoExit", "-ExecutionPolicy", "Bypass") + $arguments += @("-File", $($MyInvocation.MyCommand.Path)) + $arguments += $args + if ($PSVersionTable.PSEdition -eq 'Core') { + Start-Process -FilePath "pwsh.exe" -ArgumentList $arguments -Wait -Verb RunAs + } else { + Start-Process -FilePath "powershell.exe" -ArgumentList $arguments -Wait -Verb RunAs + } + Exit # Exit the current non-elevated process + } + } +} +# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload + +# + +#Join-Path using verbose method to support powershell 5? +#$outbase = Join-Path -Path $PSScriptRoot -ChildPath "../.." +$outbase = $PSScriptRoot +$outbase = Resolve-Path -Path $outbase +$toolsfolder = Join-Path -Path $outbase -ChildPath "tools" +if (-not(Test-Path -Path $toolsfolder -PathType Container)) { + #create folder - (can include missing intermediaries) + New-Item -Path $toolsfolder -ItemType Directory +} +$zigfolder = Join-Path $toolsfolder -ChildPath "zig" +$zigexe = Join-Path $zigfolder "zig.exe" +$releasearchive = "zig-x86_64-windows-0.15.1.zip" ;#zip on windows, tarball on every other platform +Write-Output "powershell version: $($PSVersionTable.PSVersion)" + +if (Get-Command $zigexe -ErrorAction SilentlyContinue) { + Write-Host "zig.exe is installed in tools/zig" + $zigv = tools/zig/zig.exe version 2>&1 + $stdout = $zigv | Where-Object {$_ -is [string]} + $stderr = $zigv | Where-Object {$_ -is [System.Management.Automation.ErrorRecord]} + if ($stderr) { + Write-Host "Unexpected output from tools/zig/zig.exe: $stderr" + Write-Host "Consider deleting tools/zig and re-downloading" + } else { + Write-Host "tools/zig/zig.exe version is: $stdout" + } + exit +} + +if (Get-Command "minisign" -ErrorAction SilentlyContinue) { + Write-Host "minisign is available" +} else { + Write-Host "minisign is missing. Will attempt to install using winget." + #Find-Module/Install-Module: older mechanism, available in powershell + #Find-PSResource/Install-PSResource: only available in newer pwsh etc? + $wgclient = Get-Module -ListAvailable -Name Microsoft.WinGet.Client + if (${wgclient}.Length -eq 0) { + Write-Host "Microsoft.WinGet.Client module not installed.. will try to install." + Install-PackageProvider -Name NuGet -Force + $psgallery_existing_policy = (Get-PSRepository -Name PSGallery).InstallationPolicy + if ($psgallery_existing_policy -eq "Untrusted") { + #Applies to all versions of PowerShell for the user, and is persistent for current user. + #This has risks in that a powershell session started after this call, and before we reset it, will treat PSGallery as trusted + Set-PSRepository -Name PSGallery -InstallationPolicy Trusted + } + Install-Module -Scope CurrentUser -Name Microsoft.Winget.Client -Force -Repository PSGallery + Repair-WinGetPackageManager + import-module -name Microsoft.Winget.client + + if ($psgallery_existing_policy -eq "Untrusted") { + Set-PSRepository -Name PSGallery -InstallationPolicy Untrusted + } + } else { + Write-Host "Microsoft.WinGet.Client is available" + } + $wingetversion = (Find-WinGetPackage jedisct1.minisign).Version + if ($wingetversion) { + Write-Host "Installing minisign version: ${wingetversion}" + Install-WinGetPackage -Id "jedisct1.minisign" + } + if (Get-Command "minisign" -ErrorAction SilentlyContinue) { + Write-Host "minisign is now available" + } else { + Write-Host "minisign is still not available" + } + + exit +} + +$zigpubkey = "RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U" +$mirrors_url = "https://ziglang.org/download/community-mirrors.txt" +$mirrors_response = $(Invoke-WebRequest -Uri $mirrors_url) +if ($mirrors_response.StatusCode -eq 200) { + $mirror_array = $mirrors_response.Content.TrimEnd("`r`n") -split "`r`n|`n" + #$mirror_array += "https://bogusxxx.org" #test behaviour of a bogus/down entry + $mirror_array += "https://ziglang.org" #main site + + $dict_mirrors = [ordered]@{} + $host_list = @() #same ordering as dict_mirrors + foreach ($mirror in $mirror_array) { + $uri = New-Object System.Uri($mirror) + $hostname = $uri.Host + $dict_mirrors[$hostname] = @{} + $dict_mirrors[$hostname]["uri"] = $mirror + $host_list += $hostname + #write-host "Host name: $hostname" + } + #write-host "dict: $($dict_mirrors | out-String)" + write-host "host_list: $host_list" + + $test_results = Test-Connection -TargetName $host_list -Count 1 -Ipv4 -Detailed -TcpPort 443 -Quiet + for ($i = 0; $i -lt $test_results.Count; $i++) { + $result = $test_results[$i] + if ($result) { + $targethost = $result.Target + if ($result.Status -eq "Success") { + $dict_mirrors[$targethost]["latency"] = $result.Latency + } else { + $dict_mirrors[$targethost]["latency"] = 999999 + } + } else { + $targethost = $host_list[$i] + $dict_mirrors[$targethost]["latency"] = 999999 + } + } + $list_mirror_dicts = @() + #write-host "dict tested: $($dict_mirrors | Out-String)" + foreach ($key in $dict_mirrors.Keys) { + $list_mirror_dicts += $($dict_mirrors[$key]) + } + $sorted_mirror_dicts = $list_mirror_dicts | Sort-Object -Property Latency + Write-Host "Sorted by latency: $($sorted_mirror_dicts | Format-Table -AutoSize | Out-String)" + $automation_name = "punkshell+julian@precisium.com.au_target_by_latency" + + foreach ($hostinfo in $sorted_mirror_dicts) { + $uristring = $hostinfo.uri + if ($uristring -eq "https://ziglang.org") { + $full_uristring = "${uristring}/download/0.15.1/${releasearchive}?source=${automation_name}" + $sig_uristring = "${uristring}/download/0.15.1/${releasearchive}.minisig?source=${automation_name}" + } else { + $full_uristring = "${uristring}/${releasearchive}?source=${automation_name}" + $sig_uristring = "${uristring}/${releasearchive}.minisig?source=${automation_name}" + } + Write-Host "Downloading zig from $full_uristring" + #$uriobj = [uri]$full_uristring + #$lastSegment = $uriobj.Segments[-1] + #$outfile = Join-Path $toolsfolder -ChildPath $lastSegment + $outfile = Join-Path $toolsfolder -ChildPath $releasearchive + Write-Host "Download to: $outfile" + Invoke-WebRequest -Uri $full_uristring -OutFile $outfile + + if (-not(Test-Path -Path $outfile -PathType Leaf)) { + write-Host "Failed to download zig package from $full_uristring to ${outfile} .. aborting download from $uristring" + continue + } + Write-Host "Downloading minisig signature from $sig_uristring" + Invoke-WebRequest -Uri $sig_uristring -Outfile "${outfile}.minisig" + if (-not(Test-Path -Path "${outfile}.minisig" -PathType Leaf)) { + write-Host "Failed to download minisig from $sig_uristring to ${outfile}.minisig .. aborting download from $uristring" + continue + } + + write-host "downloaded $outfile and ${outfile}.minisig - validating signature..." + #validate releasearchive (tarball/zip) + $sigresult = minisign -V -P $zigpubkey -m $outfile 2>&1 + $stdout = $sigresult | Where-Object {$_ -is [string]} + $stderr = $sigresult | Where-Object {$_ -is [System.Management.Automation.ErrorRecord]} + $is_valid = $false + if ($stderr) { + write-host "Signature validation failed with message: $stderr" + } else { + if (($stdout | Out-String).Contains("signature verified")) { + write-host $stdout + $is_valid = $true + } else { + write-host "Unexpected output from minisign: $stdout" + write-host "Did not contain 'signature verified'" + } + } + if (-not($is_valid)) { + write-Host "Couldn't verify signature from download site $uristring" + Remove-Item -Path $outfile -ErrorAction SilentlyContinue + Remove-Item -Path "${outfile}.minisig" -ErrorAction SilentlyContinue + continue + } + + Write-Host "Signature OK - extracting archive" + Expand-Archive -path $outfile -DestinationPath $toolsfolder -Force + #Remove-Item -Path "$outfile" + $zip_rootname = [System.IO.Path]::GetFileNameWithoutExtension($releasearchive) + #Rename-Item -Path + write-host "zip_rootname: $zip_rootname" + Rename-Item -Path $(Join-Path -Path $toolsfolder -ChildPath $zip_rootname) -NewName $zigfolder + Write-Host "Zig installed in ${zigfolder}" + $zigexe = Join-Path $zigfolder -ChildPath "zig.exe" + $v = Invoke-Expression "$zigexe version" + Write-Host "ZIG VERSION: ${v}" + $test_ok = 1 ;#todo + if ($test_ok) { + break + } + } + +} else { + Write-Host "Unable to retrieve list of zig community mirrors from $mirrors_url" +} + +# + +# +# + + +# -- --- --- --- --- --- --- --- +# +#tclsh $scriptname $args +#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host +# +# -- --- --- --- --- --- --- --- + + +# +# + +# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload +Exit $LASTEXITCODE +# heredoc2 for powershell to ignore block below +$1 = @' +' +: comment end hide powershell-block from Tcl \ +# This comment with closing brace should stay in place whether 'if' commented or not } +: multishell doubled-up cmd exit label - return exitcode +:exit_multishell +:exit_multishell +: \ +@REM @ECHO exitcode: !task_exitcode! +: \ +@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit) +: \ +@EXIT /B !task_exitcode! +# cmd has exited +: comment end heredoc2 \ +'@ +<# +# id:tailblock0 +# -- powershell multiline comment +#> +<# +no script engine should try to run me +# id:tailblock1 +# + +# +# -- unreachable by tcl directly if ctrl-z character is in the section above. (but file can be read and split on \x1A) +# -- Potential for zip and/or base64 contents, but we can't stop pwsh parser from slurping in the data +# -- so for example a plain text tar archive could cause problems depending on the content. +# -- final line in file must be the powershell multiline comment terminator or other data it can handle. +# -- e.g plain # comment lines will work too +# -- (for example a powershell digital signature is a # commented block of data at the end of the file) +#> + + + diff --git a/src/modules/punk-0.1.tm b/src/modules/punk-0.1.tm index f6b5aa6a..1e09252d 100644 --- a/src/modules/punk-0.1.tm +++ b/src/modules/punk-0.1.tm @@ -8259,9 +8259,29 @@ namespace eval punk { interp alias {} d/~ {} punk::nav::fs::d/~ interp alias "" x/ "" punk::nav::fs::x/ + variable pshell_path "" + # ---------------------------------------- + set pshell_path [auto_execok pwsh] ;#Still not installed by default on win10 11? + if {$pshell_path eq ""} { + #fallback to powershell 5 + #set pshell_path [auto_execok powershell] + set pshell_path powershell ;#temp + } else { + set pshell_path pwsh ;#temp + } + #todo - review run commands and handling of paths with spaces + # ---------------------------------------- - if {$::tcl_platform(platform) eq "windows"} { + + + if {$pshell_path eq ""} { + set has_powershell 0 + } else { + #todo - review powershell detection on non-windows platforms set has_powershell 1 + } + + if {$::tcl_platform(platform) eq "windows"} { interp alias {} dl {} dir /q interp alias {} dw {} dir /W/D } else { @@ -8269,8 +8289,6 @@ namespace eval punk { #interp alias {} dl {} interp alias {} dl {} puts stderr "not implemented" interp alias {} dw {} puts stderr "not implemented" - #todo - powershell detection on other platforms - set has_powershell 0 } #todo - distinguish non-preinstalled pwsh (powershell core) from powershell which is available by default @@ -8279,13 +8297,19 @@ namespace eval punk { # powershell runspaces e.g $rs=[RunspaceFactory]::CreateRunspace() # $ps = [Powershell]::Create() - interp alias {} pse {} exec >@stdout pwsh -nolo -nop -c - interp alias {} psx {} runx -n pwsh -nop -nolo -c - interp alias {} psr {} run -n pwsh -nop -nolo -c - interp alias {} psout {} runout -n pwsh -nop -nolo -c - interp alias {} pserr {} runerr -n pwsh -nop -nolo -c - interp alias {} psls {} shellrun::runconsole pwsh -nop -nolo -c ls - interp alias {} psps {} shellrun::runconsole pwsh -nop -nolo -c ps + interp alias {} pse {} exec >@stdout {*}$pshell_path -nolo -nop -c + interp alias {} psx {} runx -n {*}$pshell_path -nop -nolo -c + interp alias {} psr {} run -n {*}$pshell_path -nop -nolo -c + interp alias {} psout {} runout -n {*}$pshell_path -nop -nolo -c + interp alias {} pserr {} runerr -n {*}$pshell_path -nop -nolo -c + #interp alias {} psls {} shellrun::runconsole $pshell_path -nop -nolo -c ls + #interp alias {} psls {} shellrun::runconsole {*}$pshell_path -nop -nolo -c {ls | Select-Object Mode, @{Name='Owner';Expression={(Get-Acl $_.FullName).Owner}}, LastWriteTime, Length, Name | Format-Table} + proc psls args { + variable pshell_path + shellrun::runconsole {*}$pshell_path -nop -nolo -c {*}[string map [list %a% $args] {{ls %a% | Select-Object Mode, @{Name='Owner';Expression={(Get-Acl $_.FullName).Owner}}, LastWriteTime, Length, Name | Format-Table}}] + } + interp alias {} psls {} punk::psls + interp alias {} psps {} shellrun::runconsole {*}$pshell_path -nop -nolo -c ps } else { set ps_missing "powershell missing (powershell is MIT licensed open source and can be installed on windows and most unix-like platforms)" interp alias {} pse {} puts stderr $ps_missing diff --git a/src/modules/punk/mix/commandset/scriptwrap-999999.0a1.0.tm b/src/modules/punk/mix/commandset/scriptwrap-999999.0a1.0.tm index 521c3772..a40ae2cb 100644 --- a/src/modules/punk/mix/commandset/scriptwrap-999999.0a1.0.tm +++ b/src/modules/punk/mix/commandset/scriptwrap-999999.0a1.0.tm @@ -257,6 +257,24 @@ namespace eval punk::mix::commandset::scriptwrap { set target_labels_found [dict create] set possible_target_labels_found [dict create] set warning_target_labels_found [dict create] + + #todo - allow analysis of colon-less call. May need to check list of internal commands - but what about external ones? + #set searchregexex [list {(.*\s+|^)(@*call\s*:*)(\S.*)} {(.*\s+|^)(@*CALL\s*:*)(\S.*)} {(.*\s+|^)(@*goto\s*:*)(\S.*)} {(.*\s+|^)(@*GOTO\s*:*)(\S.*)}] + + #order of regex testing is important - test more specific entries with colon/comment before we test whitespace only version of goto/call + set searchregexes [list {(.*\s+|^)(@*call\s*:)(\S.*)} {(.*\s+|^)(@*CALL\s*:)(\S.*)} {(.*\s+|^)(@*goto\s*:)(\S.*)} {(.*\s*|.*\s+|^)(@*GOTO\s*:)(\S.*)}] + lappend searchregexes {(.*\|\|.*)(@*GOTO\s*:)(\S.*)} ;#review + lappend searchregexes {(.*\s+|^)(@*goto\s+%=.*=%\s*:)(\S.*)} + lappend searchregexes {(.*\s+|^)(@*goto\s+%=.*=%\s+)(\S.*)} + lappend searchregexes {(.*\s+|^)(@*goto\s+)(\S.*)} + + lappend searchregexes {(.*\s+|^)(@*GOTO\s+%=.*=%\s*:)(\S.*)} + lappend searchregexes {(.*\s+|^)(@*GOTO\s+%=.*=%\s+)(\S.*)} + lappend searchregexes {(.*\s+|^)(@*GOTO\s+)(\S.*)} + #review + #todo - better callsite analysis. There can be data between @GOTO or @CALL and : other than just whitespace + #e.g for @goto %= possible comment=% :mylabe%%l etc + for {set callingline_index 0} {$callingline_index < $line_count} {incr callingline_index} { set callingline_info [$objFile lineinfo $callingline_index] set callingline_payload [dict get $callingline_info payload] @@ -273,18 +291,15 @@ namespace eval punk::mix::commandset::scriptwrap { } default { - #todo - better callsite analysis. There can be data between @GOTO or @CALL and : other than just whitespace! - #todo - allow analysis of colon-less call. May need to check list of internal commands - but what about external ones? - #foreach search_regex [list {(.*\s+|^)(@*call\s*:*)(\S.*)} {(.*\s+|^)(@*CALL\s*:*)(\S.*)} {(.*\s+|^)(@*goto\s*:*)(\S.*)} {(.*\s+|^)(@*GOTO\s*:*)(\S.*)}] {} - foreach search_regex [list {(.*\s+|^)(@*call\s*:)(\S.*)} {(.*\s+|^)(@*CALL\s*:)(\S.*)} {(.*\s+|^)(@*goto\s*:)(\S.*)} {(.*\s*|.*\s+|^)(@*GOTO\s*:)(\S.*)} {(.*\|\|.*)(@*GOTO\s*:)(\S.*)}] { + foreach search_regex $searchregexes { if {[regexp $search_regex $callingline_payload _m precall call labelplus]} { #todo further checks to see if it's actually a batch script line # - - - - work out what cmd.exe considers start of 512B boundaries when scanning from a callsite #callposn affected by newlines? #set callposn [expr {$file_offset + [string length $callingline_payload]}] ;#take callposn as end of line .. review - multiline statements? - set callposn [expr {$file_offset + $callingline_len}] - + set callposn [expr {$file_offset + $callingline_len -1}] + #Note there are anomalies around target labels in bracketed sections such as IF blocks #this is bad practice - as it can lead to unbalanced braces - but batch files can still work under cmd.exe with them in some cases #e.g unbalanced trailing bracket may be ignored. @@ -1741,7 +1756,7 @@ namespace eval punk::mix::commandset::scriptwrap { #note that: #@REM ----- #@goto ^ - #:label + #:label #@REM----- # is a valid callsite - but doesn't appear to be found by the label scanner as it's own target label even though :label is on it's own line from non-batch perspective # so the caller will have to do some batch-style line processing to find all call sites diff --git a/src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd b/src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd index 835c84e4..2b28d953 100644 --- a/src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd +++ b/src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd @@ -25,7 +25,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @' @REM in batch scripts - array syntax with square brackets is a simulation of arrays or associative arrays. @REM note that many shells linked as sh do not support substition syntax and may fail - e.g dash etc - generally bash should be used in this context @SETLOCAL EnableExtensions EnableDelayedExpansion -@SET "validshelltypes= powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________ none____________" +@SET "validshelltypes= pwsh____________ powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________ none____________" @REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch @REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx @REM The horrible underscore-padded fixed-widths are to keep the batch labels aligned whilst allowing values to be set @@ -176,9 +176,9 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @' ) @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=powershell + SET selected_shelltype_trimmed=pwsh ) -@IF "!selected_shelltype_trimmed!"=="powershell" ( +@IF "!selected_shelltype_trimmed!"=="pwsh" ( REM pwsh vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time REM test availability of preferred option of powershell7+ pwsh pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL @@ -189,33 +189,35 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @' pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% SET task_exitcode=!errorlevel! ) ELSE ( - REM CALL powershell -nop -nol -c write-host powershell-found - REM powershell -nop -nol -file "%~dp0%~n0.ps1" %* - powershell -nop -nol -c set-executionpolicy -Scope Process Unrestricted; %~dp0%~n0.ps1" %arglist% + REM TODO prompt user with option to call script to install pwsh using winget + REM powershell -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% + powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %arglist% SET task_exitcode=!errorlevel! ) ) ELSE ( - IF "!selected_shelltype_trimmed!"=="wslbash" ( - CALL :getWslPath %winpath% wslpath - REM ECHO wslfullpath "!wslpath!%fname%" - %selected_shellpath_trimmed% "!wslpath!%fname%" %arglist% + IF "!selected_shelltype_trimmed!"=="powershell" ( + powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %arglist% SET task_exitcode=!errorlevel! ) ELSE ( - REM perl or tcl or sh or bash - IF NOT "x%keyRemoved%"=="x%validshelltypes%" ( - REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl - REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx - REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode - @ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!" - %selected_shellpath_trimmed% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call; + IF "!selected_shelltype_trimmed!"=="wslbash" ( + CALL :getWslPath %winpath% wslpath + REM ECHO wslfullpath "!wslpath!%fname%" + %selected_shellpath_trimmed% "!wslpath!%fname%" %arglist% + SET task_exitcode=!errorlevel! ) ELSE ( - ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes% - SET task_exitcode=66 - @REM boundary padding - @REM boundary padding - @REM boundary padding - @REM boundary padding - GOTO :exit_multishell + REM perl or tcl or sh or bash + IF NOT "x%keyRemoved%"=="x%validshelltypes%" ( + REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl + REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx + REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode + @ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!" + %selected_shellpath_trimmed% "%~dp0%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 + ) ) ) ) @@ -803,9 +805,11 @@ if ($matches.count) { $arguments = @("-NoProfile", "-NoExit", "-ExecutionPolicy", "Bypass") $arguments += @("-File", $($MyInvocation.MyCommand.Path)) $arguments += $args - - #Start-Process -FilePath "pwsh.exe" -ArgumentList "-NoProfile -NoExit -ExecutionPolicy Bypass -File $($MyInvocation.MyCommand.Path)" -Wait -Verb RunAs - Start-Process -FilePath "pwsh.exe" -ArgumentList $arguments -Wait -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 } } diff --git a/src/modules/punk/ubl-999999.0a1.0.tm b/src/modules/punk/ubl-999999.0a1.0.tm new file mode 100644 index 00000000..685251fa --- /dev/null +++ b/src/modules/punk/ubl-999999.0a1.0.tm @@ -0,0 +1,289 @@ +# -*- tcl -*- +# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from -buildversion.txt +# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm +# +# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. +# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# (C) 2025 +# +# @@ Meta Begin +# Application punk::ubl 999999.0a1.0 +# Meta platform tcl +# Meta license MIT +# @@ Meta End + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# doctools header +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +#*** !doctools +#[manpage_begin shellspy_module_punk::ubl 0 999999.0a1.0] +#[copyright "2025"] +#[titledesc {Module API}] [comment {-- Name section and table of contents description --}] +#[moddesc {-}] [comment {-- Description at end of page heading --}] +#[require punk::ubl] +#[keywords module] +#[description] +#[para] Basic UBL +#[para] https://docs.oasis-open.org/ubl/os-UBL-2.4/ +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +#*** !doctools +#[section Overview] +#[para] overview of punk::ubl +#[subsection Concepts] +#[para] - + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Requirements +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +#*** !doctools +#[subsection dependencies] +#[para] packages used by punk::ubl +#[list_begin itemized] + +package require Tcl 8.6- +#*** !doctools +#[item] [package {Tcl 8.6}] + +# #package require frobz +# #*** !doctools +# #[item] [package {frobz}] + +#*** !doctools +#[list_end] + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +#*** !doctools +#[section API] + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# oo::class namespace +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +#tcl::namespace::eval punk::ubl::class { + #*** !doctools + #[subsection {Namespace punk::ubl::class}] + #[para] class definitions + #if {[tcl::info::commands [tcl::namespace::current]::interface_sample1] eq ""} { + #*** !doctools + #[list_begin enumerated] + + # oo::class create interface_sample1 { + # #*** !doctools + # #[enum] CLASS [class interface_sample1] + # #[list_begin definitions] + + # method test {arg1} { + # #*** !doctools + # #[call class::interface_sample1 [method test] [arg arg1]] + # #[para] test method + # puts "test: $arg1" + # } + + # #*** !doctools + # #[list_end] [comment {-- end definitions interface_sample1}] + # } + + #*** !doctools + #[list_end] [comment {--- end class enumeration ---}] + #} +#} +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + + +tcl::namespace::eval punk::ubl { + # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + # Base namespace + # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + #*** !doctools + #[subsection {Namespace punk::ubl}] + #[para] Core API functions for punk::ubl + #[list_begin definitions] + + variable PUNKARGS + + + + + #proc sample1 {p1 n args} { + # #*** !doctools + # #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]] + # #[para]Description of sample1 + # #[para] Arguments: + # # [list_begin arguments] + # # [arg_def tring p1] A description of string argument p1. + # # [arg_def integer n] A description of integer argument n. + # # [list_end] + # return "ok" + #} + + + + + #*** !doctools + #[list_end] [comment {--- end definitions namespace punk::ubl ---}] +} +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# Secondary API namespace +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +tcl::namespace::eval punk::ubl::lib { + tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase + tcl::namespace::path [tcl::namespace::parent] + #*** !doctools + #[subsection {Namespace punk::ubl::lib}] + #[para] Secondary functions that are part of the API + #[list_begin definitions] + + #proc utility1 {p1 args} { + # #*** !doctools + # #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]] + # #[para]Description of utility1 + # return 1 + #} + + + + #*** !doctools + #[list_end] [comment {--- end definitions namespace punk::ubl::lib ---}] +} +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +#*** !doctools +#[section Internal] +#tcl::namespace::eval punk::ubl::system { + #*** !doctools + #[subsection {Namespace punk::ubl::system}] + #[para] Internal functions that are not part of the API + + + +#} + + +# == === === === === === === === === === === === === === === +# Sample 'about' function with punk::args documentation +# == === === === === === === === === === === === === === === +tcl::namespace::eval punk::ubl { + tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase + variable PUNKARGS + variable PUNKARGS_aliases + + lappend PUNKARGS [list { + @id -id "(package)punk::ubl" + @package -name "punk::ubl" -help\ + "Package + Description" + }] + + namespace eval argdoc { + #namespace for custom argument documentation + proc package_name {} { + return punk::ubl + } + proc about_topics {} { + #info commands results are returned in an arbitrary order (like array keys) + set topic_funs [info commands [namespace current]::get_topic_*] + set about_topics [list] + foreach f $topic_funs { + set tail [namespace tail $f] + lappend about_topics [string range $tail [string length get_topic_] end] + } + #Adjust this function or 'default_topics' if a different order is required + return [lsort $about_topics] + } + proc default_topics {} {return [list Description *]} + + # ------------------------------------------------------------- + # get_topic_ functions add more to auto-include in about topics + # ------------------------------------------------------------- + proc get_topic_Description {} { + punk::args::lib::tstr [string trim { + package punk::ubl + description to come.. + } \n] + } + proc get_topic_License {} { + return "MIT" + } + proc get_topic_Version {} { + return "$::punk::ubl::version" + } + proc get_topic_Contributors {} { + set authors {{Julian Noble }} + set contributors "" + foreach a $authors { + append contributors $a \n + } + if {[string index $contributors end] eq "\n"} { + set contributors [string range $contributors 0 end-1] + } + return $contributors + } + proc get_topic_custom-topic {} { + punk::args::lib::tstr -return string { + A custom + topic + etc + } + } + # ------------------------------------------------------------- + } + + # we re-use the argument definition from punk::args::standard_about and override some items + set overrides [dict create] + dict set overrides @id -id "::punk::ubl::about" + dict set overrides @cmd -name "punk::ubl::about" + dict set overrides @cmd -help [string trim [punk::args::lib::tstr { + About punk::ubl + }] \n] + dict set overrides topic -choices [list {*}[punk::ubl::argdoc::about_topics] *] + dict set overrides topic -choicerestricted 1 + dict set overrides topic -default [punk::ubl::argdoc::default_topics] ;#if -default is present 'topic' will always appear in parsed 'values' dict + set newdef [punk::args::resolved_def -antiglobs -package_about_namespace -override $overrides ::punk::args::package::standard_about *] + lappend PUNKARGS [list $newdef] + proc about {args} { + package require punk::args + #standard_about accepts additional choices for topic - but we need to normalize any abbreviations to full topic name before passing on + set argd [punk::args::parse $args withid ::punk::ubl::about] + lassign [dict values $argd] _leaders opts values _received + punk::args::package::standard_about -package_about_namespace ::punk::ubl::argdoc {*}$opts {*}[dict get $values topic] + } +} +# end of sample 'about' function +# == === === === === === === === === === === === === === === + + +# ----------------------------------------------------------------------------- +# register namespace(s) to have PUNKARGS,PUNKARGS_aliases variables checked +# ----------------------------------------------------------------------------- +# variable PUNKARGS +# variable PUNKARGS_aliases +namespace eval ::punk::args::register { + #use fully qualified so 8.6 doesn't find existing var in global namespace + lappend ::punk::args::register::NAMESPACES ::punk::ubl +} +# ----------------------------------------------------------------------------- + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Ready +package provide punk::ubl [tcl::namespace::eval punk::ubl { + variable pkg punk::ubl + variable version + set version 999999.0a1.0 +}] +return + +#*** !doctools +#[manpage_end] + diff --git a/src/modules/punk/ubl-buildversion.txt b/src/modules/punk/ubl-buildversion.txt new file mode 100644 index 00000000..f47d01c8 --- /dev/null +++ b/src/modules/punk/ubl-buildversion.txt @@ -0,0 +1,3 @@ +0.1.0 +#First line must be a semantic version number +#all other lines are ignored. diff --git a/src/scriptapps/getzig.bash b/src/scriptapps/getzig.bash new file mode 100644 index 00000000..860e2767 --- /dev/null +++ b/src/scriptapps/getzig.bash @@ -0,0 +1,15 @@ +#mkdir -p ./zig + +#tarball="zig-x86_64-windows-0.15.1.zip" +#tarball="zig-x86_64-freebsd-0.15.1.tar.xz" +tarball="zig-x86_64-linux-0.15.1.tar.xz" + +automation_name="punkshell+julian@precisium.com.au_target_by_latency" +uristring="https://ziglang.org" +full_uristring="${uristring}/download/0.15.1/${tarball}?source=${automation_name}" +echo "Unimplemented: Download from ${full_uristring} and extract manually" +#wget $full_uristring -O ./zig/zig-linux-x86_64-0.10.1.tar.xz +#tar -xf ./zig/zig-linux-x86_64-0.10.1.tar.xz -C ./zig --strip-components=1 +#rm ./zig/zig-linux-x86_64-0.10.1.tar.xz +#echo "Zig installed." +#./zig/zig version diff --git a/src/scriptapps/getzig.ps1 b/src/scriptapps/getzig.ps1 new file mode 100644 index 00000000..923f9751 --- /dev/null +++ b/src/scriptapps/getzig.ps1 @@ -0,0 +1,186 @@ + +#Join-Path using verbose method to support powershell 5? +#$outbase = Join-Path -Path $PSScriptRoot -ChildPath "../.." +$outbase = $PSScriptRoot +$outbase = Resolve-Path -Path $outbase +$toolsfolder = Join-Path -Path $outbase -ChildPath "tools" +if (-not(Test-Path -Path $toolsfolder -PathType Container)) { + #create folder - (can include missing intermediaries) + New-Item -Path $toolsfolder -ItemType Directory +} +$zigfolder = Join-Path $toolsfolder -ChildPath "zig" +$zigexe = Join-Path $zigfolder "zig.exe" +$releasearchive = "zig-x86_64-windows-0.15.1.zip" ;#zip on windows, tarball on every other platform +Write-Output "powershell version: $($PSVersionTable.PSVersion)" + +if (Get-Command $zigexe -ErrorAction SilentlyContinue) { + Write-Host "zig.exe is installed in tools/zig" + $zigv = tools/zig/zig.exe version 2>&1 + $stdout = $zigv | Where-Object {$_ -is [string]} + $stderr = $zigv | Where-Object {$_ -is [System.Management.Automation.ErrorRecord]} + if ($stderr) { + Write-Host "Unexpected output from tools/zig/zig.exe: $stderr" + Write-Host "Consider deleting tools/zig and re-downloading" + } else { + Write-Host "tools/zig/zig.exe version is: $stdout" + } + exit +} + +if (Get-Command "minisign" -ErrorAction SilentlyContinue) { + Write-Host "minisign is available" +} else { + Write-Host "minisign is missing. Will attempt to install using winget." + #Find-Module/Install-Module: older mechanism, available in powershell + #Find-PSResource/Install-PSResource: only available in newer pwsh etc? + $wgclient = Get-Module -ListAvailable -Name Microsoft.WinGet.Client + if (${wgclient}.Length -eq 0) { + Write-Host "Microsoft.WinGet.Client module not installed.. will try to install." + Install-PackageProvider -Name NuGet -Force + $psgallery_existing_policy = (Get-PSRepository -Name PSGallery).InstallationPolicy + if ($psgallery_existing_policy -eq "Untrusted") { + #Applies to all versions of PowerShell for the user, and is persistent for current user. + #This has risks in that a powershell session started after this call, and before we reset it, will treat PSGallery as trusted + Set-PSRepository -Name PSGallery -InstallationPolicy Trusted + } + Install-Module -Scope CurrentUser -Name Microsoft.Winget.Client -Force -Repository PSGallery + Repair-WinGetPackageManager + import-module -name Microsoft.Winget.client + + if ($psgallery_existing_policy -eq "Untrusted") { + Set-PSRepository -Name PSGallery -InstallationPolicy Untrusted + } + } else { + Write-Host "Microsoft.WinGet.Client is available" + } + $wingetversion = (Find-WinGetPackage jedisct1.minisign).Version + if ($wingetversion) { + Write-Host "Installing minisign version: ${wingetversion}" + Install-WinGetPackage -Id "jedisct1.minisign" + } + if (Get-Command "minisign" -ErrorAction SilentlyContinue) { + Write-Host "minisign is now available" + } else { + Write-Host "minisign is still not available" + } + + exit +} + +$zigpubkey = "RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U" +$mirrors_url = "https://ziglang.org/download/community-mirrors.txt" +$mirrors_response = $(Invoke-WebRequest -Uri $mirrors_url) +if ($mirrors_response.StatusCode -eq 200) { + $mirror_array = $mirrors_response.Content.TrimEnd("`r`n") -split "`r`n|`n" + #$mirror_array += "https://bogusxxx.org" #test behaviour of a bogus/down entry + $mirror_array += "https://ziglang.org" #main site + + $dict_mirrors = [ordered]@{} + $host_list = @() #same ordering as dict_mirrors + foreach ($mirror in $mirror_array) { + $uri = New-Object System.Uri($mirror) + $hostname = $uri.Host + $dict_mirrors[$hostname] = @{} + $dict_mirrors[$hostname]["uri"] = $mirror + $host_list += $hostname + #write-host "Host name: $hostname" + } + #write-host "dict: $($dict_mirrors | out-String)" + write-host "host_list: $host_list" + + $test_results = Test-Connection -TargetName $host_list -Count 1 -Ipv4 -Detailed -TcpPort 443 -Quiet + for ($i = 0; $i -lt $test_results.Count; $i++) { + $result = $test_results[$i] + if ($result) { + $targethost = $result.Target + if ($result.Status -eq "Success") { + $dict_mirrors[$targethost]["latency"] = $result.Latency + } else { + $dict_mirrors[$targethost]["latency"] = 999999 + } + } else { + $targethost = $host_list[$i] + $dict_mirrors[$targethost]["latency"] = 999999 + } + } + $list_mirror_dicts = @() + #write-host "dict tested: $($dict_mirrors | Out-String)" + foreach ($key in $dict_mirrors.Keys) { + $list_mirror_dicts += $($dict_mirrors[$key]) + } + $sorted_mirror_dicts = $list_mirror_dicts | Sort-Object -Property Latency + Write-Host "Sorted by latency: $($sorted_mirror_dicts | Format-Table -AutoSize | Out-String)" + $automation_name = "punkshell+julian@precisium.com.au_target_by_latency" + + foreach ($hostinfo in $sorted_mirror_dicts) { + $uristring = $hostinfo.uri + if ($uristring -eq "https://ziglang.org") { + $full_uristring = "${uristring}/download/0.15.1/${releasearchive}?source=${automation_name}" + $sig_uristring = "${uristring}/download/0.15.1/${releasearchive}.minisig?source=${automation_name}" + } else { + $full_uristring = "${uristring}/${releasearchive}?source=${automation_name}" + $sig_uristring = "${uristring}/${releasearchive}.minisig?source=${automation_name}" + } + Write-Host "Downloading zig from $full_uristring" + #$uriobj = [uri]$full_uristring + #$lastSegment = $uriobj.Segments[-1] + #$outfile = Join-Path $toolsfolder -ChildPath $lastSegment + $outfile = Join-Path $toolsfolder -ChildPath $releasearchive + Write-Host "Download to: $outfile" + Invoke-WebRequest -Uri $full_uristring -OutFile $outfile + + if (-not(Test-Path -Path $outfile -PathType Leaf)) { + write-Host "Failed to download zig package from $full_uristring to ${outfile} .. aborting download from $uristring" + continue + } + Write-Host "Downloading minisig signature from $sig_uristring" + Invoke-WebRequest -Uri $sig_uristring -Outfile "${outfile}.minisig" + if (-not(Test-Path -Path "${outfile}.minisig" -PathType Leaf)) { + write-Host "Failed to download minisig from $sig_uristring to ${outfile}.minisig .. aborting download from $uristring" + continue + } + + write-host "downloaded $outfile and ${outfile}.minisig - validating signature..." + #validate releasearchive (tarball/zip) + $sigresult = minisign -V -P $zigpubkey -m $outfile 2>&1 + $stdout = $sigresult | Where-Object {$_ -is [string]} + $stderr = $sigresult | Where-Object {$_ -is [System.Management.Automation.ErrorRecord]} + $is_valid = $false + if ($stderr) { + write-host "Signature validation failed with message: $stderr" + } else { + if (($stdout | Out-String).Contains("signature verified")) { + write-host $stdout + $is_valid = $true + } else { + write-host "Unexpected output from minisign: $stdout" + write-host "Did not contain 'signature verified'" + } + } + if (-not($is_valid)) { + write-Host "Couldn't verify signature from download site $uristring" + Remove-Item -Path $outfile -ErrorAction SilentlyContinue + Remove-Item -Path "${outfile}.minisig" -ErrorAction SilentlyContinue + continue + } + + Write-Host "Signature OK - extracting archive" + Expand-Archive -path $outfile -DestinationPath $toolsfolder -Force + #Remove-Item -Path "$outfile" + $zip_rootname = [System.IO.Path]::GetFileNameWithoutExtension($releasearchive) + #Rename-Item -Path + write-host "zip_rootname: $zip_rootname" + Rename-Item -Path $(Join-Path -Path $toolsfolder -ChildPath $zip_rootname) -NewName $zigfolder + Write-Host "Zig installed in ${zigfolder}" + $zigexe = Join-Path $zigfolder -ChildPath "zig.exe" + $v = Invoke-Expression "$zigexe version" + Write-Host "ZIG VERSION: ${v}" + $test_ok = 1 ;#todo + if ($test_ok) { + break + } + } + +} else { + Write-Host "Unable to retrieve list of zig community mirrors from $mirrors_url" +} diff --git a/src/scriptapps/getzig_wrap.toml b/src/scriptapps/getzig_wrap.toml new file mode 100644 index 00000000..0a7b5e53 --- /dev/null +++ b/src/scriptapps/getzig_wrap.toml @@ -0,0 +1,21 @@ + + +[application] + template="punk.multishell.cmd" + as_admin=false + + scripts=[ + "getzig.ps1", + "getzig.bash" + ] + + default_outputfile="getzig.cmd" + default_nextshellpath="/usr/bin/env bash" + default_nextshelltype="bash" + + #valid nextshelltype entries are: tcl perl pwsh powershell bash. + #nextshellpath entries must be 64 characters or less. + + win32.nextshellpath="pwsh" + win32.nextshelltype="pwsh" + win32.outputfile="getzig.cmd"