notexactly
notexactly

Reputation: 1018

How do I fix this positional parameter error (PowerShell)?

I have written this PowerShell instruction to add the given path to the list of Microsoft Defender exclusions in a new PowerShell process (with elevated permissions):

Start-Process powershell {Add-MpPreference -ExclusionPath "my/path/to/exe"; Read-Host;} -Verb "RunAs"

Surprisingly, I get this error:

Add-MpPreference : A positional parameter cannot be found that accepts argument '-'.

However, if I run the line below, the output (hi) is displayed in the new process successfully:

Start-Process powershell {echo "hi"; Read-Host} -Verb "RunAs"

What is wrong with the first PowerShell instruction? How can I fix this?

Upvotes: 5

Views: 38467

Answers (2)

user16136127
user16136127

Reputation:

Use Named Parameters

For people coming later and looking for help with the error message:

<cmdlet>: A positional parameter cannot be found that accepts argument '<arg>'.

A quick fix is to use named parameters. Named parameters always work.

# Doesn't work. The parameter name *is not* given. 
Get-ChildItem 32

# Works. The parameter name *is* given. 
Get-ChildItem -Depth 32

List Positional Parameters

Alternatively, you might check if your cmdlet has any positional parameters. You can search the documentation. But a quick way is to have PowerShell do the work. Use the one-liner below. And just replace "Get-ChildItem" with the cmdlet you are interested in. Remember, if the output only shows "Named" then the cmdlet does not accept positional parameters. Below, there are two positional parameters: Path and Filter.

Get-Help -Name "Get-ChildItem" -Parameter * | 
  Sort-Object -Property position | 
  Select-Object -Property name, position                                  

name          position
----          --------
Path          0
Filter        1
Attributes    Named
Depth         Named
Directory     Named
Exclude       Named
File          Named
FollowSymlink Named
Force         Named
Hidden        Named
Include       Named
LiteralPath   Named
Name          Named
ReadOnly      Named
Recurse       Named
System        Named

What is a positional parameter?

A parameter is text that is added to a command to change how the command works. The word "parameter" generally refers to both the parameter name and parameter value. Parameter names are preceded with a hyphen '-'

# | cmdlet       | parameter name  
# v              v     v parameter value
  Get-ChildItem -Path './'

Most cmdlets offer a shortcut which omits the parameter name. These are known as positional parameters. And cmdlet developers are not required to support positional parameters.

# | cmdlet
# v              v positional parameter
  Get-ChildItem './'

Since positional parameters are unnamed, the only way to differentiate them is by their position in the statement hence the name positional parameter. You can find out what position is associated with the parameter you are interested in by using the Get-Help cmdlet, by using Microsoft Docs online, or by checking with your cmdlet vendor's documentation. You might have to do a bit of searching because parameters are indexed by name and not position within the help pages. The first position is numbered 0. And subsequent positions increase by 1. So the first four positions are 0, 1, 2, 3 ... Below, we can see that the Path parameter for Get-ChildItem is accepted at position 0. This is indicated on the line that begins with "Position?".

Get-Help -Name "Get-ChildItem" -Parameter "Path" 

-Path <string[]>
    
    Required?                    false
    Position?                    0
    Accept pipeline input?       true (ByValue, ByPropertyName)
    Parameter set name           Items
    Aliases                      None
    Dynamic?                     false
    Accept wildcard characters?  false

Named parameters will show "Named" as their position values.

Get-Help -Name "Get-ChildItem" -Parameter "LiteralPath"

-LiteralPath <string[]>
    
    Required?                    true
    Position?                    Named
    Accept pipeline input?       true (ByPropertyName)
    Parameter set name           LiteralItems
    Aliases                      PSPath, LP
    Dynamic?                     false
    Accept wildcard characters?  false

Find out more by reading the parameter help topic online.

Error Message Details

I wanted to be able to explain the error message from the original inquiry. But I did not come to a conclusion.

Looking at the documentation, Add-MpPreference is unusual in that it has no positional parameters.

I do not have a copy of the Add-MpPreference cmdlet on my system to test with. I found that Get-Unique is a similar cmdlet. And I was able to produce a similar error with Get-Unique:

@("hello","hello") | get-unique -

         Get-Unique: A positional parameter cannot be found that accepts argument '-'. 
# Add-MpPreference : A positional parameter cannot be found that accepts argument '-'.

So we have to think about what if any positional parameters are available. And we can see how the code and error message might compare.

Starting with PowerShell 7, we can get more information about the last error message with Get-Error. Earlier versions of PowerShell will print most of this information by default. But PowerShell 7 switched to the simpler error messages.

If you scroll down to PositionMessage, it will tell you what line of code caused the execution error. And it will draw a little diagram where the offending expression is underlined. This makes it easier to connect the error to specific syntax.

Get-Error                                                

...

PositionMessage  : At line:1 char:22
                       + @("hello","hello") | get-unique -
                       +                      ~~~~~~~~~~~~

Check for Bad Characters

Sometimes if we copy and paste from online, we can pick up typesetting characters which are appropriate for print but not for code. Examples include smart quotes (which curl) and various dashes like the emdash (which is longer than a standard dash). Conhost and Windows Terminal should be able to cope with these. But I'm not sure about scripts or other hosts (like within NSIS).

One way to check is to go to the shell and use Format-Hex to check a short snippet of suspect code. We'll need to use a here-string to preserve the original formatting.

Below I copied the error message to the shell and checked it out. I wanted to know if the hyphen at the end has the correct character code. We can see the code at byte 0x51 or row 50 and column 01. The value there is 0x2D. We can check the correct value for a hyphen by typing it directly at the command line. Be sure to enclose it in quotes. After our second check, we see that the correct value is 0x2D. Since the value we were investigating matched the correct value, it doesn't look like the hyphen was the problem. If you do this yourself, make sure to copy from your code editor instead of any error message which is a compromise I had to use here.

@"
Add-MpPreference : A positional parameter cannot be found that accepts argument '-'.
"@ | Format-Hex

   Label: String (System.String) <44085160>

          Offset Bytes                                           Ascii
                 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
          ------ ----------------------------------------------- -----
0000000000000000 41 64 64 2D 4D 70 50 72 65 66 65 72 65 6E 63 65 Add-MpPreference
0000000000000010 20 3A 20 41 20 70 6F 73 69 74 69 6F 6E 61 6C 20  : A positional 
0000000000000020 70 61 72 61 6D 65 74 65 72 20 63 61 6E 6E 6F 74 parameter cannot
0000000000000030 20 62 65 20 66 6F 75 6E 64 20 74 68 61 74 20 61  be found that a
0000000000000040 63 63 65 70 74 73 20 61 72 67 75 6D 65 6E 74 20 ccepts argument 
0000000000000050 27 2D 27 2E                                     '-'.


'-' | Format-Hex

   Label: String (System.String) <6ECC1D05>

          Offset Bytes                                           Ascii
                 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
          ------ ----------------------------------------------- -----
0000000000000000 2D                                              -

powershell.exe Command Parameter

I wanted to step through the command piece by piece to see if there were any subtle syntax errors. I ran into an unrelated bug on my system which kept me from getting very far. But, after thinking a bit, I'm not sure why any of the examples work. In this case, Start-Process should coerce the arguments to [String[]]. And powershell.exe should interpret the first string as a file name and not a cmdlet. It is true that powershell.exe can recognize a script block without a parameter name. But that doesn't apply here because of the coercion to [String[]].

If the accepted answer (having to do with Start-Process and quoting) does not help, then you might look into using the appropriate parameter for powershell.exe. Or you might need to explore the rabbit hole of quoting and escapes when calling programs from PowerShell.

#             | Interprets strings as file names. 
#             |           |----------- String passed to powershell.exe --------------| 
#             v           |  not a file                                              |  
Start-Process powershell {Add-MpPreference -ExclusionPath "my/path/to/exe"; Read-Host;} -Verb "RunAs"
Start-Process powershell "Add-MpPreference -ExclusionPath 'my/path/to/exe'"             -Verb "RunAs"

powershell.exe Help | Microsoft Docs

pwsh.exe Help | Microsoft Docs

General information on passing arguments | Microsoft Docs

Arguments for external executables aren't correctly escaped | GitHub Issue

Quoting issues with Start-Process specifically | GitHub Issue

Quoting advice for Azure CLI | GitHub Doc

Upvotes: 2

notexactly
notexactly

Reputation: 1018

With @mklement0's help (see the comment above), I realized I had to use a double-quoted string with Start-Process to make the instruction work:

Start-Process powershell "Add-MpPreference -ExclusionPath 'my/path/to/exe'" -Verb "RunAs"

Upvotes: 4

Related Questions