How to call a powershell script with arguments in Nodejs [duplicate] - node.js

In GNU/Linux I would do:
PROGPATH=/long/and/complicated/path/to/some/bin
$PROGPATH/program args...
but in Powershell if I try this:
$PROGPATH=\long\and\complicated\path\to\some\bin
$PROGPATH\program args...
I get:
At script.ps1:2 char:...
+ $PROGPATH\program args ...
+ ~~~~~~~~
Unexpected token '\program' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParseException
+ FullyQualifiedErrorId : UnexpectedToken
So how do I do this simple thing I know how to do in bash, in Powershell?

js2010's helpful answer shows the correct solution:
Because your command name/path contains a variable reference ($PROGPATH/...), you must invoke it with &.
The same applies if a grouping expression, (...) is used, or a subexpression, $(...) is involved.
Additionally, the same applies if a command name/path is quoted ('...' or "...")[1], as is required if the path contains spaces, for instance.
To put it differently: Direct invocation is only supported if the command name/path is a verbatim, unquoted string[1]; in all other cases, & must be used.
As for why:
&, the call operator is necessary to force interpretation of a statement as a command, i.e. to have it parsed in argument mode (see below), so as to result in command execution rather than expression evaluation.
PowerShell has two fundamental parsing modes:
argument mode, which works like a traditional shell, where the first token is a command name/path, such as a cmdlet or an external program, with subsequent tokens representing the arguments, which only require quoting if they contain shell metacharacters (chars. with special meaning to PowerShell, such as spaces to separate tokens).
expression mode, which works like expressions in programming languages.
PowerShell decides based on a statement's first token what parsing mode to apply:
If, among other things, the first token starts with a variable reference or is a quoted string, PowerShell parses in expression mode.
In expression mode, \ starts a new token, and unrecognized token \program results in the syntax error you saw.
(If you had used /, it would have been interpreted as the division operator, and program wouldn't be a valid divisor operand.)
[1] Note that if your executable path is a literal string (doesn't contain variable references of expressions) you may alternatively `-escape individual characters (spaces) in lieu of enclosing entire string in '...' or "...", in which case & is then not necessary; e.g.:
C:\Program` Files\Notepad++\notepad++.exe
With a literal string you can even employ partial single- or double-quoting as long as the first token is unquoted; e.g.:
C:\"Program Files"\Notepad++\notepad++.exe

Use the call operator "&". https://ss64.com/ps/call.html
Related: Executing a command stored in a variable from PowerShell
$progpath = 'c:\windows\system32'
& $progpath\notepad somefile.txt
Something with a space:
& 'C:\Program Files\internet explorer\iexplore' yahoo.com
Other options, adding to the path:
$env:path += ';C:\Program Files\internet explorer'
iexplore yahoo.com
And backquoting the spaces:
C:\Program` Files\internet` explorer\iexplore yahoo.com

Related

Can I get scape characters still behave as such for a string provided by f:read() in Lua?

I'm working on a simple localization function for my scripts and, although it's starting to work quite well so far, I don't know how to avoid scape/special characters to be shown in UI as part of the text after feeding the widgets with the strings returned by f:read().
For example, if in a certain Strings.ES.txt's line I have: Ignorar \"Etiquetas de capa\", I'd expect backslashes didn't end showing up just like when I feed the widget with a normal string between doble quotes like: "Ignorar \"Etiquetas de capa\"", or at least have a way to avoid it. I've been trial-and-erroring with tostring() and load() functions and different (surely nonsense 🙄) concatenations like: load(tostring("[[" .. f:read()" .. ]]")) and such without any success, so here I'm again...
Do someone know if there is a way to get scape characters in a string returned by f:read() still behave as special as when they are found in a regular one?
I don't know how to avoid [e]scape/special characters to be shown in UI as part of the text
What you want is to "unescape" or "unquote" a string to interpret escape sequences as if it were parsed as a quoted string by Lua.
[...] with the strings returned by f:read() [...]
The fact that this string was obtained using f:read() can be ignored; all that matters is that it is a string literal without quotes using quoted string escapes.
I've been trial-and-erroring with tostring() and load() functions and different [...] concatenations like: load(tostring("[[" .. f:read()" .. ]]")) and such without any success [...]
This is almost how to do it, except you chose the wrong string literal type: "Long" strings using pairs square brackets ([ and ]) do not interpret escape sequences at all; they are intended for including long, raw, possibly multiline strings in Lua programs and often come in handy when you need to represent literal strings with backslashes (e.g. regular expressions - not to be confused with Lua patterns, which use % for escapes, and lack the basic alternation operator of regular expressions).
If you instead use single or double quotes to wrap the string, it will work fine:
local function unescape_string(escaped)
return assert(load(('return "%s"'):format(escaped)))()
end
this will produce a tiny Lua program (a "chunk") for each string, which just consists of return "<contents>". Recall that Lua chunks are just functions. Thus you can simply call the function to obtain the value of the string it returns. That way, Lua will interpret the escape sequences for us. The same approach is often used to use Lua for reading data serialized as Lua code.
Note also the use of assert for error handling: load returns nil, err if there is a syntax error. To deal with this gracefully, we can wrap the call to load in assert: assert returns its first argument (the chunk returned by load) if it is truthy; otherwise, if it is falsy (e.g. nil in this case), assert errors, using its second argument as an error message. If you omit the assert and your input causes a syntax error, you will instead get a cryptic "attempt to call a nil value" error.
You probably want to do additional validation, especially if these escaped strings are user-provided - otherwise a malicious string like str"; os.execute("...") can trivially invoke a remote code execution (RCE) vulnerability, allowing it to both execute Lua e.g. to block (while 1 do end), slow down or hijack your application, as well as shell commands using os.execute. To guard against this, searching for an unescaped closing quote should be sufficient (syntax errors e.g. through invalid escapes will still be possible, but RCE should not be possible excepting Lua interpreter bugs):
local function unescape_string(escaped)
-- match start & end of sequences of zero or more backslashes followed by a double quote
for from, to in escaped:gmatch'()\\*()"' do
-- number of preceding backslashes must be odd for the double quote to be escaped
assert((to - from) % 2 ~= 0, "unescaped double quote")
end
return assert(load(('return "%s"'):format(escaped)))()
end
Alternatively, a more robust (but also more complex) and presumably more efficient way of unescaping this would be to manually implement escape sequences through string.gsub; that way you get full control, which is more suitable for user-provided input:
-- Single-character backslash escapes of Lua 5.1 according to the reference manual: https://www.lua.org/manual/5.1/manual.html#2.1
local escapes = {a = '\a', b = '\b', f = '\b', n = '\n', r = '\r', t = '\t', v = '\v', ['\\'] = '\\', ["'"] = "'", ['"'] = '"'}
local function unescape_string(escaped)
return escaped:gsub("\\(.)", escapes)
end
you may implement escapes here as you see fit; for example, this misses decimal escapes, which could easily be implemented as escaped:gsub("\\(%d%d?%d?)", string.char) (this uses coercion of strings to numbers in string.char and a replacement function as second argument to string.gsub).
This function can finally be used straightforwardly as unescape_string(f:read()).

Powershell, use (space-separated) string as arguments to a program

I've read this and it doesn't solve my problem.
I have a space-separated string, let's say $MyString = "arg1 arg2". Suppose I have a command line program called MyProgram, which accepts an arbitrary number of positional arguments, so it can be run like MyProgram arg1 arg2. However doing MyProgram $MyString doesn't work, and neither does MyProgram ($MyString -split ' ') nor MyProgram $($MyString -split ' '). I get the same error which basically says that it doesn't recognise the argument "arg1 arg2", which I guess is because it still thinks it's one argument containing a space rather than two arguments. In practice, $MyString may be quite huge and is read from a file. How do I make this work?
Oh I just found out how LOL. I should have thought of this sooner; basically, just use splatting The following worked for me:
$MyArray = $($MyString -split " ")
MyProgram #MyArray
Explanation: The first line converts the string into an array of strings split by space (" "); The $(...) notation around a command captures the output of the command, which I then assign to $MyArray. Then, instead of using $MyArray with a dollar sign $, I use it with # to splat the array of strings into arguments for MyProgram.
tl;dr
For calling PowerShell commands you indeed need splatting in order to pass the elements of an array as individual, positional arguments; this requires defining the array in an auxiliary variable that can then be passed with sigil # in lieu of $ to request splatting:
$myArray = -split $myString # See below for limitations, bottom section for fix
MyPowerShellCommand #myArray # Array elements are passed as indiv. arguments.
While this technique also works with external programs, it isn't strictly necessary there, and you can pass an array directly to achieve the same effect:
MyExternalProgram (-split $myString) # Array elements are passed as indiv. args.
Note that (...) rather than $(...) is used to pass the expression as an argument. (...) is usually sufficient and generally preferable, because $(...) can have side effects - see this answer for details.
Just to bring the post you link to in your question and your answer here together:
First, to be clear: neither answer, due to splitting by spaces only will deal properly with arguments inside the argument-list string that have embedded spaces (and therefore, of necessity use embedded quoting), e.g., $myString = "arg1 `"arg2 with spaces`" arg3" would not work as expected - see the bottom section for a solution.
Leaving that aside, the difference is:
When calling an external program, as in the linked post, passing an array causes each element to become its own argument.
That is, myExternalProgram (-split $MyString) would work.
Note that I'm using the unary form of the -split operator for more flexible tokenization, which splits by any non-empty run of whitespace while ignoring leading and trailing whitespace (same as awk's default behavior).
When calling a PowerShell command, as in your case, an array is by default passed as-is, as a whole, as a single argument.
To achieve the same effect as with external programs, i.e. to pass the array's elements as individual, positional arguments, you indeed have to use splatting, i.e. you have to:
save the array in a variable first: $myArray = -split $myString,
which you can then pass as as a splatted argument by using # instead of $ as the sigil: MyPowerShellCommand #myArray
Do note that when calling PowerShell commands it is more common - and more robust - to use hashtable- rather than an array-based splatting, as it allows you to explicitly bind to parameters by name rather than by position - and PowerShell commands often have parameters that can only be bound by name.
E.g., if MyPowerShellCommand accepts parameters -Foo and -Bar, you could use:
$myArgs = #{ Foo='foo value'; Bar='bar value '}; MyPowerShellCommand #myArgs
If you do want to handle argument-list strings that have arguments with embedded quoting:
$myString = 'arg1 "arg2 with spaces" arg3'
$myArray = (Invoke-Expression ('Write-Output -- ' + $myString -replace '\$', "`0")) -replace "`0", '$$'
Note: Invoke-Expression (iex) should generally be avoided, but the extra precautions taken in this particular command make its use safe.
$myArray is then a 3-element array with verbatim elements arg1, arg2 with spaces and arg3, which can again be used as shown above.
See this answer for an explanation of the technique.
These work for me ($args is reserved). -split on the left side splits on whitespace. Or you can get-content from a file where each argument is on a seperate line. You might run into a limit with how long a commandline can be. Piping that list in or loading it from a file might be a better approach.
echo hi > file.txt
$args2 = 'hi','file.txt'
findstr $args2
# hi
$args2 = 'hi','file.txt'
& findstr $args2
# hi
$args2 = 'hi file.txt'
findstr (-split $args2)
# hi
findstr ($args2 -split ' ')
# hi

What $() syntax means for Groovy language?

I found this in Groovy Syntax documentation at 4.6.1. Special cases:
As slashy strings were mostly designed to make regexp easier so a few
things that are errors in GStrings like $() or $5 will work with
slashy strings.
What $() syntax means? give some usage examples please
I also found it at Define the Contract Locally in the Repository of the Fraud Detection Service:
body([ // (4)
"client.id": $(regex('[0-9]{10}')),
loanAmount : 99999
])
but I don't understand what $() means when used with regex('[0-9]{10}').
It means nothing (or what you make of it). There are two places, you
are addressing, but they have nothing to do with each other.
The docs just mention this as "you can use slashy strings to write
things, that would give you an error with a GString" - the same is true
for just using '-Strings.
E.g.
"hello $()"
Gives this error:
unknown recognition error type: groovyjarjarantlr4.v4.runtime.LexerNoViableAltException
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
/tmp/x.groovy: 1: token recognition error at: '(' # line 1, column 9.
"hello $()"
The parser either wants a { or any char, that is a valid first char
for a variable (neither ( nor 5 is).
The other place you encountered $() (in Spring cloud contract), this
is just a function with the name $.
Form the docs 8. Contract DSL:
You can set the properties inside the body either with the value method or, if you use the Groovy map notation, with $()
So this is just a function, with a very short name.
E.g. you can try this yourself:
void $(x) { println x }
$("Hello")

When do values passed to a parameter require quotes?

I have been working with Exchange Online unified groups and Microsoft Teams teams in powershell: New-UnifiedGroup, Set-UnifiedGroup, Get-UnifiedGroup, New-Team, Get-Team etc. I have been creating groups and teams from PowerShell.
I am interested in understanding a bit better when a value for a parameter requires quotes and when it does not.
When I import from CSV, it seems the values are automatically interpreted as strings. When I supply them to a parameter that requires a string, the value does not require quotes even if it has spaces e.g New-UnifiedGroup -DisplayName $item.displayName does not require quotes even when the display name has spaces.
But when I want to create a team from an existing group, and I get the ID of the group, the group id requires quotes: New-Teams -GroupId "$group.ExternalDirectoryObjectId". In this case the parameter -GroupId requires a string value, although the ExternalDirectoryObjectId that it requires is not a string.
Is there a rule that a value does not require quotes if it is a string, and a string value is expected? Does it help to declare a variable as a string before passing it to a parameter that requires a string? For example, if I have a $path variable, I usually have to provide it as -Path "$path". If I declared the path as [String]$path =, would I then not need to use the quotes in -Path $path
Generally, only ever use quoting in PowerShell to explicitly pass a value as a string ([string]).
String literals require quoting if they contain any of the following: spaces or, more generally, PowerShell metacharacters[1], and commands or expressions as part of a larger string (which must then be enclosed in $(...) - see below).
To pass the value of a variable, one of its properties, or even the result of a method call on it, you do not need quoting in PowerShell, which will pass the resulting value with its original data type; however, when the value is bound to its target parameter, PowerShell may automatically convert it to the parameter's type.
If the target parameter is [string]-typed (as is the case with New-Team's -GroupId parameter), PowerShell will automatically convert any non-string argument to a string, essentially by calling .ToString() on it[2]. If the resulting string isn't the right representation, you must perform explicit stringification, by way of an expression or command.
E.g., both -GroupId $groupId and -GroupId $group.ExternalDirectoryObjectId would work - even if the resulting string contains embedded spaces or other PowerShell metacharacters.
If you need to pass an object's property, a method call, or any type of command or expression as part of a larger string, enclose the argument in "..." and use $(...), the subexpression operator around the expression / command (e.g., "$($group.ExternalDirectoryObjectId)/more"; referencing a variable by itself inside "..." does not require $(...)).
"$group.ExternalDirectoryObjectId" definitely does not work as intended, because only variable reference $group by itself is recognized - and stringified - whereas the .ExternalDirectoryObjectId part is treated literally - see first link below for why.
Further reading:
Overview of PowerShell's expandable strings (string interpolation, "...")
String literals in PowerShell (bottom section)
How unquoted tokens are parsed as arguments.
[1] The metacharacters are (some only need quoting if at the start of the argument):
<space> ' " ` , ; ( ) { } | & < > # #
[2] The exact stringification rules, where culture-sensitivity factors in as well, are detailed in this answer.
Generally, PowerShell has a very flexible automatic type-conversion system whose rules are complex and not explicitly documented - a peek at the source code may help.
PowerShell always tries to automatically convert a given value to the target type, where the target type may be dictated by a parameter's type or the (usually) LHS operand of an operator-based expression (e.g., 42 + "1" yields 43).
That's strange. Usually you only need quotes to pass a literal string that has a space in it.
get-childitem -path 'foo 2'
I can pass an object property without quotes usually:
$a = [pscustomobject]#{path = 'foo 2'}
get-childitem -path $a.path
This is more rare, but if a string looks like an array element, I've found I have to quote it:
select-xml -XPath "//*[#a='hi']" -Path file.xml
Even using something like an integer works without quotes, because it can be 'coerced' into a string.

How to match line & error in Sublime Text 3?

How can I match this error in the build with regex to locate line and file with result_line_regex & result_file_regex?
project4.dpr(9) Hint: H2164 Variable 'I' is declared but never used in 'Project3'
I have tried this but it won't work.
"result_file_regex": "^.*\\(.*)/.?(.*)$",
"result_line_regex": "^([^\\]*)\.(\w+)$",
As already mentioned in the comments, file_regex is the setting that gets passed to result_line_regex (have a look at the run() method signature of class ExecCommand in Packages/Default/exec.py).
A good regex in your case would be ^([\w-]+\.\w+)\((\d+)\). The first group captures something like my-file.ext and the second one the digit(s) in parentheses.
In order to set that expression in a string in the json file you need to escape each backslash with another backslash (\ is the escape character in strings), so it becomes:
"file_regex": "^([\\w-]+\\.\\w+)\\((\\d+)\\)"
Notice that the matched file has to be in the path of the file that is active when triggering the build system. If you want it to be relative to a certain path no matter where you trigger the build, you can also pass a working directory like:
"working_dir": "/path/to/my/source"
This will be set as result_base_dir in the output view.

Resources