Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does hyphen/dash parameter mean to PowerShell?

Tags:

powershell

I've found out that, if I pass only a dash to an argument of PowerShell 5.1 script on Windows 10, like this:

powershell.exe -File Test.ps1 -

I get a strange error message saying:

C:\path\Test.ps1 : Cannot process argument because the value of argument "name" is not valid. Change the value o f the "name" argument and run the operation again.

  • CategoryInfo : InvalidArgument: (:) [Test.ps1], PSArgumentException
  • FullyQualifiedErrorId : Argument,Test.ps1

The Test.ps1 is only:

echo "foo"

The actual problem I face though is that, when the script declares any mandatory parameter:

param (
    [Parameter(Mandatory)]
    $value
)

echo "foo"

Then executing the script the same way (with - argument) does nothing at all. No output. No error message. It just hangs for a few seconds. And then a control returns to a command prompt.

C:\path>powershell.exe -File Test.ps1 -

C:\path>_

What does the - mean to PowerShell (5.1)?


On the contrary, with PowerShell 2.0 on Windows 7, I get script usage in this case:

C:\path>powershell.exe -File Test.ps1 -
Test.ps1 [-value] <Object> [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]


C:\path>_

What makes sense (a missing mandatory parameter).

And without the mandatory parameter declaration, the script works (prints it output):

C:\path>powershell.exe -File Test.ps1 -
foo

C:\path>_
like image 318
Martin Prikryl Avatar asked Jul 25 '19 10:07

Martin Prikryl


2 Answers

The behavior should be considered a bug - something that starts with - but isn't a valid parameter name should be passed as a positional argument rather than reporting an error.

The bug affects:

  • Windows PowerShell (as of v5.1.18362.145) - it is unclear if a fix will ever be made.

    • As you state, whether you (a) get the error message (... the value of argument "name" is not valid ...) or (b) the - is quietly ignored depends on whether your parameter has a parameter attribute such as [Parameter(Mandatory)][1] and/or your param() block has a [CmdletBinding()] attribute (if so, (b) applies).
  • PowerShell Core 6.x - that is, the problem will be fixed in v7 (current as of this writing: v7.0.0-preview.3); I don't know if 6.2.2, the stable version current as of this writing, will be fixed - we'll see what happens to the bug report on GitHub you've filed.


As for a workaround (works analogously in PowerShell Core):

Use -Command instead of -File.

While that changes the semantics of how the command line is parsed[2], in simple cases such as this one the difference won't matter:

C:\> powershell -Command ./Test.ps1 -  # note the "./"

Note the ./, because using -Command (-c) makes PowerShell parse the arguments as if they were PowerShell code, and the usual restrictions re executing scripts by filename only apply (to prevent accidental execution of a file in the current directory, you need a path component to explicitly signal that intent, hence prefix ./ or .\ is needed).

If your script file path needed quoting, you'd have to use quoting and prepend &, the call operator; e.g.:

C:\> powershell -Command "& \"./Test.ps1\" -"

[1] Adding a [Parameter()] attribute to a declared parameter implicitly makes the enclosing script/function an advanced one, in which case different parsing rules apply. The [CmdletBinding[] attribute, which is applied to a param(...) block as a whole, explicitly marks a script / function as an advanced one.

[2] See this answer for the differences between how -File and -Command arguments are parsed.

like image 188
mklement0 Avatar answered Sep 16 '22 18:09

mklement0


This isn't an answer but I'm curious as well. If you've already figured it out I'd be interested in what you found. Otherwise, in case it helps, Powershell -h says everything after -file is the script and any arguments passed to it.

-File
    Runs the specified script in the local scope ("dot-sourced"), so that the
    functions and variables that the script creates are available in the
    current session. Enter the script file path and any parameters.
    File must be the last parameter in the command, because all characters
    typed after the File parameter name are interpreted
    as the script file path followed by the script parameters.

Tokenizer reads it in as a CommandArgument.

Powershell >> $Errors = $Null    
Powershell >> [System.Management.Automation.PSParser]::Tokenize("powershell -file test.ps1 -", [ref]$Errors)[3]

Content     : -
Type        : CommandArgument
Start       : 26
Length      : 1
StartLine   : 1
StartColumn : 27
EndLine     : 1
EndColumn   : 28

So it seems like the issue is further up the chain but I couldn't find a simple way to call the Parser functions to test.

I do see that there's a case that shouldn't occur where the error is swallowed and null returned which might cause it to just stop like it does in your example

like image 38
RiverHeart Avatar answered Sep 20 '22 18:09

RiverHeart