Reputation: 1018
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
Reputation:
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
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
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.
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 -
+ ~~~~~~~~~~~~
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 -
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
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