Using Stack from Powershell, how do I pass test arguments that include a space? - powershell

I need to pass the string "fail log" as a tasty pattern argument when running tasty tests out of stack.
This is quite straight forward in bash:
PS C:\Pyrethrum> stack test --fast --ta "-p \"fail log\""
But trying to achieve the same using Powershell is driving me nuts:
PS C:\Pyrethrum> stack test --fast --ta "-p ""fail"" "
works (runs tests) when there is no space
PS C:\Pyrethrum> stack test --fast --ta "-p ""fail log"" "
Error parsing targets: Directory not found: log
PS C:\Pyrethrum> stack test --fast --ta "-p `"fail log`""
tries install a package called log
PS C:\Pyrethrum> stack test --fast --ta "-p \"fail log\""
option --ta: unterminated string: endOfInput
PS C:\Pyrethrum> stack test --fast --ta "-p /fail log/ "
... builds but pattern needs quotes
pyrethrum-0.1.0.0: test (suite: pyrethrum-test, args: -p /fail log/)
option -p: Could not parse pattern
What is the correct command line to get this to run in Powershell ?

Unfortunately, the way PowerShell passes arguments to external programs is broken up to at least PowerShell 7.2.x, and requires you to not just satisfy PowerShell's own syntax requirements with respect to embedded ", but to additionally escape them for the target executable, typically with \:
# `" is needed for PowerShell, \ is needed for stack
stack test --fast --ta "-p \`"fail log\`""
Since your outer "..." string references no PowerShell variables and contains no subexpressions, i.e., since no string interpolation is required, you can use a '...', a literal PowerShell string instead, which simplifies matters a bit:
stack test --fast --ta '-p \"fail log\"'
In short: To pass " characters embedded in an argument to an external program from PowerShell:
use \`" inside "..."
use \" inside '...'
See this answer for details, also with respect to a potential future fix and a helper function that hides the bug.

Related

Passing a powershell variable value to an adb shell command

Good day to all. After google-ing and trying various solutions, I'm a bit stuck with the following easy (at least it seemed so) task:
I have a powershell variable, say:
$simpleString = "Hello World and stuff"
I'm testing an Android app via ADB, where I need to pass this variable's value as a string:
.\adb.exe shell input text "$simpleString"
I get error
.\adb.exe : Error: Invalid arguments for command: text
followed by reminder on how to use "input" command by adb.
Update:
I've also tried the following workaround:
$myCmd = Write-output "adb.exe shell input text `"$simpleString`""
thus building a valid command for CMD and then run it via:
cmd /c $myCmd
but I still get same issue
Any help will be much appreciated, thank you.
OK, after some additional trial and error, I found a solution that worked for me (using back-ticks, single quotes and "wrapping" the command into another variable:
$myCmd = Write-output "adb.exe shell input text `'$simpleString`'"
& cmd /c $myCmd
Spelling-out the first command for easier perception:
$myCmd = Write-output {double quotes}adb.exe shell input text {back-tick}{single quote}$simpleString{back-tick}{single quote}{double quotes}

Windows Powershell and npm: passing "^" character in parameter string

Windows 11 & Powershell 7.2.5: I'm trying to pass a regular expression in a parameter to my node.js app. The expression, ^#Test, contains a ^ character, which is apparently also used to denote escape sequences in Powershell. Let's say this is my app:
const param = process.argv[2];
console.log(param);
When I run it this way:
node index.js "^#Test"
it works correctly and prints ^#Test
But if I configure a script in package.json like this:
"scripts": {
"start": "node index.js"
}
And I run:
npm run start "^#Test"
the leading ^ gets trimmed and it prints only #Test
Curiously, it does not get trimmed if ^ character is surrounded by a space on either side. So the following work correctly:
npm run start "^ #Test" // prints "^ #Test", correct
npm run start " ^#Test" // prints " ^#Test", correct
npm run start " ^ #Test" // prints " ^ #Test", correct
I tried escaping with ^^, `^ or ^, but neither works:
npm run start "^^#Test" // prints "#Test"
npm run start "`^#Test" // prints "#Test"
npm run start "\^#Test" // prints "\\#Test"
This also does not make a difference:
npm run start -- "^#Test" // prints "#Test"
With different quotes and without quotes it also does not work:
npm run start ^#Test // prints "#Test"
npm run start '^#Test' // prints "#Test"
Is this a bug or am I escaping it wrong?
tl;dr
Note: The following workarounds are required only if your argument does not contain spaces.
For literal arguments, use --%, the stop-parsing token:
npm run start --% "^#Test"
For variable-based arguments, use embedded quoting:
$var = 'Test'
npm run start "`"^#$var`""
You're seeing the confluence of two surprising behaviors, one by cmd.exe and the other by PowerShell:
On Windows, npm's entry point is a batch file, npm.cmd, and is therefore subject to cmd.exe's parsing rules.
When a batch file is called from outside cmd.exe, cmd.exe still - inappropriately - parses the command line as if it had been submitted from inside a cmd.exe session. (Unfortunately, it has always worked this way and is unlikely to change).
Therefore, any ^ characters in an unquoted argument are interpreted as cmd.exe escape character; if ^ precedes a character with no special meaning (to cmd.exe), it is simply removed (e.g., ^# turns into just #).
PowerShell, which has its own quoting syntax and escape character, of necessity needs to rebuild command lines behind the scenes, so as to use only "..."-based quoting (double-quoting), as that is the only form of quoting CLIs are generally expected to support.
In this rebuilding process, double-quoting is employed on demand, namely based on whether a given argument contains spaces.
Thus, despite you having specified "^#test" on the original command line, as parsed by PowerShell, on the rebuilt command line it is unquoted ^#test that is passed, which causes npm, due to being a batch file, to effectively drop the ^, as explained above.
Note:
PowerShell's behavior is defensible, as CLIs shouldn't parse their command lines as if they were shell command lines, which is what cmd.exe unfortunately does when batch files are called. Specifically, ^ should not be special when a batch file is called from outside a cmd.exe sessions.
GitHub issue #15143 proposes that PowerShell implement accommodations for cmd.exe and other high-profile CLIs on Windows, so as to minimize such edge cases - unfortunately, it looks like that won't happen.

How to run Bash commands with a PowerShell Core alias?

I am trying to run a Bash command where an alias exists in PowerShell Core.
I want to clear the bash history. Example code below:
# Launch PowerShell core on Linux
pwsh
# Attempt 1
history -c
Get-History: Missing an argument for parameter 'Count'. Specify a parameter of type 'System.Int32' and try again.
# Attempt 2
bash history -c
/usr/bin/bash: history: No such file or directory
# Attempt 3
& "history -c"
&: The term 'history -c' is not recognized as the name of a cmdlet, function, script file, or operable program.
It seems the issue is related to history being an alias for Get-History - is there a way to run Bash commands in PowerShell core with an alias?
history is a Bash builtin, i.e. an internal command that can only be invoked from inside a Bash session; thus, by definition you cannot invoke it directly from PowerShell.
In PowerShell history is an alias of PowerShell's own Get-History cmdlet, where -c references the -Count parameter, which requires an argument (the number of history entries to retrieve).
Unfortunately, Clear-History is not enough to clear PowerShell's session history as of PowerShell 7.2, because it only clear's one history (PowerShell's own), not also the one provided by the PSReadLine module used for command-line editing by default - see this answer.
Your attempt to call bash explicitly with your command - bash history -c - is syntactically flawed (see bottom section).
However, even fixing the syntax problem - bash -c 'history -c' - does not clear Bash's history - it seemingly has no effect (and adding the -i option doesn't help) - I don't know why.
The workaround is to remove the file that underlies Bash's (persisted) command history directly:
if (Test-Path $HOME\.bash_history) { Remove-Item -Force $HOME\.bash_history }
To answer the general question implied by the post's title:
To pass a command with arguments to bash for execution, pass it to bash -c, as a single string; e.g.:
bash -c 'date +%s'
Without -c, the first argument would be interpreted as the name or path of a script file.
Note that any additional arguments following the first -c argument would become the arguments to the first argument; that is, the first argument acts as a mini-script that can receive arguments the way scripts usually do, via $1, ...:
# Note: the second argument, "-", becomes $0 in Bash terms,
# i.e. the name of the script
PS> bash -c 'echo $0; echo arg count: $#' self one two
self
arg count: 2

Call a batch file from PowerShell with proper exit code reporting while avoiding doubling of carets

I suspect there is no good solution, but perhaps I'm overlooking something:
What I'm after is a way to:
(a) call a batch file from PowerShell in a way that robustly reflects its - implicit or explicit - exit code in PowerShell's automatic $LASTEXITCODE variable.
Notably, calling a batch file that exits with, say, whoami -nosuch || exit /b, should result in $LASTEXITCODE reflecting whoami's exit code, i.e. 1. This is not the case when you invoke a batch file (by name or path) from PowerShell: the exit code is 0 (by contrast, inside a cmd.exe session %ERRORLEVEL% is set to 1).
Also note that the invocation should remain integrated with PowerShell's output streams, so I am not looking for solutions based on System.Diagnostics.Process.
Furthermore, I have no knowledge of or control over the batch files getting invoked - I'm looking for a generic solution.
(b) without double-quoted arguments passed to the batch file getting altered in any way, and without cmd.exe's behavior getting modified in any way; notably:
^ characters should not be doubled (see below).
Enabling delayed expansion with /V:ON is not an option.
The only way I know how to solve (a) is to invoke the batch file via cmd /c call.
Unfortunately, this violates requirement (b), because the use of call seemingly invariably doubles ^ characters in arguments. (And, conversely, not using call then doesn't report the exit code reliably).
Is there a way to satisfy both requirements?
Note that PowerShell is only the messenger here: The problem lies with cmd.exe, and anyone calling a batch file from outside a cmd.exe session is faced with the same problem.
Example (PowerShell code):
# Create a (temporary) batch file that echoes its arguments,
# provokes an error, and exits with `exit /b` *without an explicit argument*.
'#echo off & echo [%*] & whoami -nosuch 2>NUL || exit /b' | Set-Content test.cmd
# Invoke the batch file and report the exit code.
.\test.cmd "a ^ 2"; $LASTEXITCODE
The output should be:
["a ^ 2"]
1
However, in reality the exit code is not reported:
["a ^ 2"]
0 # !! BROKEN
If I call with cmd /c call .\test.cmd instead, the exit code is correct, but the ^ characters are doubled:
PS> cmd /c call .\test.cmd "a ^ 2"; $LASTEXITCODE
["a ^^ 2"] # !! BROKEN
1 # OK
I've no idea why this works, but it does:
cmd /c '.\test.cmd "a ^ 2" & exit'
$LASTEXITCODE
Output:
["a ^ 2"]
1
Kudos to beatcracker for finding an effective workaround in his answer; let me add some background information and guidance:
First, to be clear, no workaround should be necessary; cmd.exe's behavior is clearly a bug.
cmd /c '.\test.cmd "a ^ 2" || exit' - i.e. || rather than & - is what one would expect to be an effective workaround too. The fact that only &, which unconditionally sequences commands, works, indicates that even cmd.exe-internally the failure status of the batch file isn't yet known as part of the same statement - only afterwards - which appears to be another manifestation of the bug.
Why an explicit exit call following the batch-file call as part of the same statement does relay the batch file's (zero or nonzero) exit code correctly is anyone's guess, but it seems to work.
Fortunately, the workaround is also effective for solving related exit-code problems in batch files that do not contain explicit exit /b / exit calls - see this answer.
Syntax considerations:
From PowerShell, the alternative to passing a single command-string is to pass individual arguments and escape the & character as `& (using `, the "backtick", PowerShell's escape character) so as to prevent PowerShell from interpreting it (quoting it as '&' would work too):
cmd /c .\test.cmd "a ^ 2" `& exit
From an environment that doesn't involve a shell, such as when launching from Task Scheduler, the `-escaping of & is not needed (and mustn't be used).
Not having to enclose the entire for-cmd.exe command in quotes makes it easier to pass arguments that (a) individually require double quotes and (b) involve references to PowerShell variables and/or expressions, given that the latter requires use of "..." rather than '...':
# Passing *individual* arguments makes double-quoting easier.
PS> cmd /c .\test.cmd "Version = $($PSVersionTable.PSVersion)" `& exit; $LASTEXITCODE
["Version = 7.2.0-preview.4"]
1
Using quoting of the entire for-cmd.exe command would be awkward in this case, due to the need to escape the argument-specific " chars.:
# Embedded double quotes must now be `-escaped.
PS> cmd /c ".\test.cmd `"Version = $($PSVersionTable.PSVersion)`" & exit"
["Version = 7.2.0-preview.4"]
1
The Native module (authored by me; install it from the PowerShell Gallery with Install-Module Native) comes with function ie, which:
automatically applies the above workaround.
generally compensates for problems arising from PowerShell's broken argument-passing to external programs (see this answer).
# After having run Install-Module Native:
# Use of function `ie` applies the workaround behind the scenes.
PS> ie .\test.cmd "Version = $($PSVersionTable.PSVersion)"; $LASTEXITCODE
["Version = 7.2.0-preview.4"]
1
The hope is that what function ie does will become a part of PowerShell itself, as part of the upcoming (in PowerShell v7.2) PSNativeCommandArgumentPassing experimental feature that is intended as an opt-in fix to the broken argument-passing - see GitHub issue #15143

Pass in a command string to AutoHotkey

I want to run a single AutoHotkey command. A script seems kindof overkill.
In bash and powershell, you can run a command by passing it in as a string to the shell:
pwsh -Command ls
bash -c ls
Is there a way to do this with AutoHotKey.exe? In the documentation, all I see is that you can pass the name of a script file to execute. If powershell supported process substitution <(ls), I could do
AutoHotKey.exe <(echo "ls")
But I don't think there's a way to do this in powershell.
Is there another way other than creating a complicated version of process substitution myself?
The linked docs state:
[v1.1.17+]: Specify an asterisk (*) for the filename to read the script text from standard input (stdin). For an example, see ExecScript().
For instance, from PowerShell:
'MsgBox % "Hello, world."' | AutoHotKey.exe *
I'm not sure if you're looking for what the other answer states, or then for this what I'm about to write:
You can pass arguments into an AHK script and those arguments are then found from the built in variable A_Args.
Example AHK script:
for each, arg in A_Args
output .= arg "`n"
MsgBox, % output
PowerShell command:
& "C:\Path\To\My\Script.ahk" arg1 arg2 "this is the 3rd argument" arg4
This would be if you have AHK installed. If you have some portable AHK setup, you'd pass in the example script to AutoHotkey.exe and then the desired arguments.