Escaping a literal question mark in PowerShell
I came across some PowerShell code like Get-Process | ?{ $_.ProcessName -Match "^ex.*" }
but didn’t know what ?{ … }
does. I only knew %{ … }
, which is the
shorthand notation of a For-Each
where $_
refers to the current element.
So my journey began.
The Get-Alias cmdlet
A quick internet search for powershell shorthand notations lead me to the
blog post Weekend Scripter: Deciphering Windows PowerShell Shorthand.
The first thing it mentions is Get-Alias
which lets you look up shorthands:
Get-Alias iex # positional argument
Get-Alias -Name iex # named argument
CommandType Name Version Source
----------- ---- ------- ------
Alias iex -> Invoke-Expression
It also works the other way around, looking up the shorter aliases of commands:
Get-Alias -Definition Invoke-Expression
CommandType Name Version Source
----------- ---- ------- ------
Alias iex -> Invoke-Expression
Too many alias lookup results
When I tried Get-Alias ?
it answered my question (?
is equivalent to
Where-Object
), but to my surprise some other single character aliases were
listed too:
CommandType Name Version Source
----------- ---- ------- ------
Alias % -> ForEach-Object
Alias ? -> Where-Object
Alias h -> Get-History
Alias r -> Invoke-History
So there is obviously some special meaning to the question mark. However, the
PowerShell documentation About Special Characters does not list it in the
table of recognized escape sequences. I gave it a shot and tried
Get-Alias `?
anyway, but the result remained the same. I also tried a few other common
escaping patterns such as Get-Alias \?
and wrapping in quotes, but it either
returned the same list or nothing.
Escaping wildcard characters
The Get-Alias documentation mentions that you can
Enter […] a pattern, such as “s*”. Wildcards are permitted.
It doesn’t say anything about ?
though. The page I found About Wildcards
lists the question mark together with the other supported wildcards *
and
[…]
, confirms that it matches one character in that position, but does not
contain any information about how to escape them.
My search continued to Supporting Wildcard Characters in Cmdlet Parameters, which has a section about Handling Literal Characters in Wildcard Patterns:
If the wildcard pattern you specify contains literal characters that should not be interpretted as wildcard characters, use the backtick character (`) as an escape character. When you specify literal characters in the PowerShell API, use a single backtick. When you specify literal characters at the PowerShell command prompt, use two backticks.
So Get-Alias ``?
it is:
CommandType Name Version Source
----------- ---- ------- ------
Alias ? -> Where-Object
What is the PowerShell API?!
I couldn’t find any meaningful information about the mentioned PowerShell API,
in which case you need a single backtick to escape ?
only. I arrived at the
conclusion that it refers to the usage of PowerShell in a .NET language such as
C#. The following code for a small console demo application supports my theory:
using System;
using System.Management.Automation;
namespace TestPowershellApi
{
class Program
{
static void Main(string[] args)
{
PowerShell ps = PowerShell
.Create()
.AddCommand("Get-Alias")
.AddArgument("`?"); // positional argument
//.AddParameter("Name", "`?"); // named argument
foreach (PSObject result in ps.Invoke())
{
Console.WriteLine("{0} -> {1}",
result.Members["Name"].Value,
result.Members["Definition"].Value
);
}
}
}
}
? -> Where-Object
Note that there are also PowerShell reference assemblies available as
separate packages for certain use cases, but the built-in
System.Management.Automation.PowerShell
is all you need if you want to
execute PowerShell commands in C#.
Quadruple escaping for Invoke-Expression
In the (rare?) case that you want to evaluate a command or expression string in a PowerShell script or the PowerShell command prompt, then you need not 1, not 2, but 4 backticks:
Invoke-Expression "Get-Alias ````?"
My assumption is that one level of escaping is necessary for the command line
itself, and the other because everything that is entered by the user is a
string at first. For example, 40+2
is a sequence of characters. However, it
gets evaluated to 42
. This requires parsing and casting the digits to numbers.
If every input is implicitly converted, then it could be necessary to escape a
single backtick with another one in order to not consume it and interpret the
subsequent character as wildcard instead of as literal.
Summary
?
is an alias of Where-Object
, which selects objects from a collection
based on their property values. You can find that out with Get-Alias
. To look
up just this alias and not every single-character alias, you must escape
the question mark:
-
Single backtick if you use the PowerShell API, such as using
System.Management.Automation
in C#:PowerShell.Create().AddCommand("Get-Alias").AddArgument("`?")
-
Two backticks in PowerShell scripts and the command prompt in general:
Get-Alias ``?
-
Four backticks if it’s a string that you want to run as code:
Invoke-Expression "Get-Alias ````?"
The following commands are identical:
Get-Process | ?{ $_.ProcessName -Match "^ex.*" }
Get-Process | Where-Object { $_.ProcessName -Match "^ex.*" }
Get-Process | Where-Object ProcessName -Match "^ex.*"
- Script block format with shorthand
- Script block format
- Comparison statement format
- Bonus: very short variant with slightly different behavior:
ps | Where ProcessName -M "^ex.*"