PowerShell command only works without spaces between arguments - powershell

I try certain source codes using PowerShell to extract an password protected archive using 7zip:
This command doesn' work (7zip is an alias for $7zipPath):
& 7zip x "$zipFile" -o "$output" -p $zipFilePassword
I get the this error:
Command Line Error:
Too short switch:
But when I remove the spaces between the variables -o and -p, the archive can be extracted. This behaviour confuses me with other command line tools like git etc.? Why is it so?

The behavior is specific to 7-Zip (7z.exe) and applies to whatever program (shell) you invoke it from:
Deviating from widely used conventions observed by CLIs such as git, 7z requires that even switches (options) that have mandatory arguments, such as -o and -p, have the argument directly attached to the switch name - no spaces are allowed:
& 7zip x $zipFile -o"$output" -p"$zipFilePassword"
Note that you normally need not enclose variable references in PowerShell in "..." (note how $zipFile isn't), even if they contain spaces. However, in order to attach them directly to switch names, you do.
Alternatively, you could enclose the entire token - switch name and argument - in double quotes:
& 7zip x $zipFile "-o$output" "-p$zipFilePassword"

Related

Trying to create an alias to include options in powershell

My goal is to have an alias to run a command with -o at the end.
PS C:\Users\XXXX> vlab -o <various different arguments>
I have tried to setup an alias, but Set-Alias recognizes -o as a parameter Option, and
placing it in single or double quotes fails
> set-alias vlab 'vlab -o'
> vlab
vlab : The term 'vlab -o' is not recognized as the name of a cmdlet, function, script file, or operable program.
also setup a profile.ps1 with a function, but this just hangs when I run it.
Function VlabSpeed {
vlab -o
}
Set-Alias vlab VlabSpeed
Is it possible to set an alias like this?
if so, how?
Thanks
To recap:
PowerShell aliases can only map a name to another command name or executable file path - unlike in, say, Bash, defining arguments as part of the mapping is not supported.
To incorporate arguments, you indeed need a function rather than an alias:
Inside that function, you can use #args to pass any arguments through to another command, as Santiago Squarzon points out.
There is no need for both an alias and a function - just name your function directly as the short name you desire.
If your short (function) name is the same as the file name of the external program that it wraps, you need to disambiguate the two to avoid infinite recursion:
On Windows:
Use the external program's filename extension, typically .exe:
function vlab { vlab.exe -o #args }
On Unix-like platforms:
Given that executables there (typically) have no filename extension, use the full path to disambiguate:
function vlab { /path/to/vlab -o #args }

Multi parameters in Powershell Bash/Zsh command

Unable to run the following Bash/Zsh command in Powershell:
$KeyPath = Join-Path -Path $this.Plate -ChildPath "install/tekton.key"
kubectl create secret docker-registry regcred `
--docker-server="https://gcr.io" `
--docker-username=_json_key `
--docker-email="name#org.iam.gserviceaccount.com" `
--docker-password="$(cat $KeyPath)"
I get error:
error: exactly one NAME is required, got 5
See 'kubectl create secret docker-registry -h' for help and examples
If I run this command directly in bash it works:
kubectl create secret docker-registry regcred --docker-server="https://gcr.io" --docker-username=_json_key --docker-email="name#org.iam.gserviceaccount.com" --docker-password="$(cat ./tekton.key)"
I don't know if it's the cause of your problem, but there are two potential problems:
An expanded value that contains spaces causes PowerShell to double-quote the argument as a whole when it rebuilds the command line behind the scenes (on Windows):
For instance, if $(cat $KeyPath) ($(Get-Content $KeyPath)) expands to one two, PowerShell passes "--docker-password=one two" behind the scenes, not --docker-password="one two".
Whether this changes the meaning of the argument depends on how the target program parses its command line - I don't know what kubectl does.
If you do need to address this, escape the enclosing " (double quotes) with `` ``` (the backtick, PowerShell's escape character to make PowerShell pass your argument in the original syntax form:
--docker-password=`"$(cat ./tekton.key)`"
Note that - unlike in POSIX-like shells such as Bash and Zsh - you normally do not enclose a variable reference or subexpression in "..." in order to pass it through safely; e.g., --foo=$someVar or --foo=$(Get-Date) work fine, even if $someVar or the output from Get-Date contains spaces or wildcard characters.
If file $KeyPath contains multiple lines, the lines are concatenated with spaces in the argument:
For instance, if the file contains "a`nb`n" ("`n" being a newline), PowerShell will pass
"--docker-password=a b".
By contrast, POSIX-like shells such as Bash or Zsh will preserve the interior newlines, while trimming (any number of) trailing ones.
On a side note: PowerShell's handling of embedded double-quoting in arguments passed to external programs is broken - see this answer.

Run command line in PowerShell

I know there are lots of posts regarding this, but nothing worked for me.
I am trying to run this command line in PowerShell:
C:/Program Files (x86)/ClamWin/bin/clamd.exe --install
I have this in PowerShell:
&"C:/Program Files (x86)/ClamWin/bin/clamd.exe --install"
But all this does is execute clamd.exe, ignoring the --install parameter
How can I get the full command line to run?
Josef Z's comment on the question provides the solution:
& "C:/Program Files (x86)/ClamWin/bin/clamd.exe" --install # double-quoted exe path
or, given that the executable path is a literal (contains no variable references or subexpressions), using a verbatim (single-quoted) string ('...'):
& 'C:/Program Files (x86)/ClamWin/bin/clamd.exe' --install # single-quoted exe path
As for why your own solution attempt failed: The call operator, &, expects only a command name/path as an argument, not a full command line.
Invoke-Expression accepts an entire command line, but that complicates things further and can be a security risk.
As for why this is the solution:
The need for quoting stands to reason: you need to tell PowerShell that C:/Program Files (x86)/ClamWin/bin/clamd.exe is a single token (path), despite containing embedded spaces.
&, the so-called call operator, is needed, because PowerShell has two fundamental parsing modes:
argument mode, which works like a traditional shell, where the first token is a command name, 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);
that is why --install need not, but can be quoted (PowerShell will simply remove the quotes for you before passing the argument to the target executable.)
expression mode, which works like expressions in programming languages.
PowerShell decides based on a statement's first token what parsing mode to apply:
If the first token is a quoted string - which we need here due to the embedded spaces in the executable path - or a variable reference (e.g., $var ...), PowerShell parses in expression mode by default.
A quoted string or a variable reference as an expression would simply output the string / variable value.
However, given that we want to execute the executable whose path is stored in a quoted string, we need to force argument mode, which is what the & operator ensures.
Generally, it's important to understand that PowerShell performs nontrivial pre-processing of the command line before the target executable is invoked, so what the command line looks like in PowerShell code is generally not directly what the target executable sees.
If you reference a PowerShell variable on the command line and that variable contains embedded spaces, PowerShell will implicitly enclose the variable's value in double quotes before passing it on - this is discussed in this answer to the linked question.
PowerShell's metacharacters differ from that of cmd.exe and are more numerous (notably, , has special meaning in PowerShell (array constructor), but not cmd.exe - see this answer).
To simplify reuse of existing, cmd.exe-based command lines, PowerShell v3 introduced the special stop-parsing symbol, --%, which turns off PowerShell's normal parsing of the remainder of the command line and only interpolates cmd.exe-style environment-variable references (e.g., %USERNAME%).

How to change the directory to program files using Powershell?

I would like to open a C:\Program Files\R\R-3.2.0\bin\Rscript.exe. For that I am trying to change the directory. I figured that the error is in opening Program files. Following is the code
cd Program Files\R\R-3.2.0\bin
Error: A positional parameter cannot be found that accepts argument Files
Unlike command.com/cmd.exe, PowerShell follows much more consistent rules and in the failing case Program and Files\R..bin are parsed as two separate arguments, where the second is invalid in context (as cd only accepts a single non-named argument).
To fix this use quotes, eg.
cd "C:\Program Files"
With the quotes it is parsed as a string value which is supplied as a single argument (the string itself does not include the quotes, again unlike cmd.exe rules).
FWIW, cd is an alias for Set-Location. Run get-help cd for the details on how it can be used - include which optional (and named) parameters it does support.
You need to put the path in quotes if it contains a space:
cd 'C:\Program Files\R\R-3.2.0\bin'
Either single or double quotes will work.

How can I ensure my autocompleted spaces are fed into my function properly?

I'm using zsh, and am trying to write a function to operate on a URL and a pathname:
function my-function
{
somecommand --url $1 $(readlink -f $2)
}
(to complicate things somewhat, the function actually uses sh syntax, as it is sourced from my ~/.zshrc using a trick like this). The readlink is there to expand symlinks and ensure directories such as . are evaluated correctly (the directory name is stored for later use by somecommand).
When I type a command from the command-line like this:
my-function http://example.org/example /tmp/myexampledirectory
... it works fine, even if I autocomplete the directory name. However, if the directory name contains spaces, zsh completes it like this:
my-function http://example.org/example /tmp/My\ Example\ Directory
For most "normal" commands (cp, mv, etc.) that never seems to cause a problem. However, in my case, somecommand sees $2 as only being /tmp/My - presumably the rest is seen as another argument.
How can I avoid this situation? I would prefer not to alter the standard zsh autocompletion, but rather find a way for my function to handle this.
The zsh completion system works very well here, and the solution is very simple, just put double-quotes around the readlink argument in the script:
somecommand --url $1 $(readlink -f "$2")
The point is that without quotes readlink removes backslashes which escape whitespaces. Compare three results:
1. Without backslashes and quotes readlink -f assumes that there are three different files/directories (with default path in current directory) and produces
$ readlink -f /tmp/My Example Directory
/tmp/My
/home/jimmij/Example
/home/jimmij/Directory
2. With escaping backslashes but without quotes readlink -f understands that there is only one directory, but removes backslashes from output, so that somecommand takes three separate arguments
$ readlink -f /tmp/My\ Example\ Directory
/tmp/My Example Directory
3. With backslashes and with double-quotes readlink -f gives the output with backslashes what is (most probably) expected by somecommand
$ readlink -f "/tmp/My\ Example\ Directory"
/tmp/My\ Example\ Directory
BTW, as a rule of thumb: if there are any problems with whitespaces in the shell-like scripts (bash, zsh, whatever) the first thing to play with is different quotation marks around variables.