Why does powershell auto completion use single-quotes and escapes special characters?
Because the `
-escaping isn't string-interpolation escaping (which only applies to "..."
strings), it is escaping of wildcard-pattern metacharacters, which happen to use the same escape character as string-interpolation escaping, `
.
In other words: by design, the `
chars. are retained as part of the verbatim '...'
string, to be interpreted later during wildcard-pattern processing.
See the "Background information" section below, which discusses when PowerShell's tab completion assumes that a filename argument isn't just a literal name but a wildcard pattern, as is the case here.
What is the correct way to deal with such parameters?
To prevent the escaping, make sure that your script's (first positional) parameter is named
-LiteralPath
(parameter variable name $LiteralPath
).
A quick demonstration with a function:
# Tab-completing an argument that binds to a -LiteralPath parameter deactivates
# escaping.
function foo { param($LiteralPath) $LiteralPath }; foo .\file[bla].dot
This requirement is unfortunate, since the name -LiteralPath
isn't always appropriate - and, unfortunately, using a different name in combination with an [Alias('LiteralPath')
attribute does not work.
If you need to use a different parameter name, you can roll your own, unescaped file-name tab completion, via an [ArgumentCompleterAttribute()]
attribute:
function foo {
param(
[ArgumentCompleter({
param($unused1, $unused2, $wordToComplete)
(Get-Item ('{0}*' -f ($wordToComplete -replace '[][]', '``$&'))).Name
})]
$SomeParam
)
$SomeParam # echo for diagnostic purposes
}
foo .\file[bla].dot
Note:
This simplistic implementation only works with files in the current directory, but can be adapted to work with all paths.
Note the use of verbatim ``
in the replacement string, which is a workaround for a bug described in GitHub issue #7999.
An alternative is to not try to fix the tab-completion behavior and instead unescape the argument received, which should work even when the argument isn't escaped:
function foo { param($SomeParam) [WildcardPattern]::Unescape($SomeParam) }
# The escaped path is now unescaped inside the function.
foo '.\file`[bla`].dot'
Note: In the unlikely event that you have file names that contain literal `[
and `]
sequences and the user types or pastes such a literal name, the approach would malfunction. In fact, even tab-completion with escaping fails in this case, as of v7.1.0; e.g. verbatim `[
turns to escaped ``[
, even though it should be ```[
Caveat:
- If you do use file paths that contain
[
and ]
unescaped, you have to make sure that you use the -LiteralPath
parameter when you pass these paths to file-processing cmdlets such as Get-Item
, Get-ChildItem
or Get-Content
later.
Background information:
For any parameter name other than -LiteralPath
, notably -Path
, PowerShell assumes that the parameter is designed to accept wildcard expressions and therefore escapes wildcard metacharacters that occur verbatim in file names with `
; since *
and ?
cannot be part of file names (at least on Windows), this effectively applies to [
and ]
.
Note that `
is not a string-interpolation escape character in this case: given that a verbatim single-quoted string ('...'
) is used in the completion, the `
characters are preserved. However, if you use such a string in a context where a wildcard expression is expected, it is the wildcard engine that then recognizes `
as its escape character.
Conversely, this means that if you use an expandable (interpolating) double-quoted string ("..."
), you must use ``
to escape wildcard metacharacters - and tab-completion indeed does that if you start your argument with "
As of v7.1, PowerShell's tab-completion behavior with respect to file names is somewhat unfortunate:
It is reasonable default behavior to perform file-name completion for any parameters whose completion behavior isn't implied by their type (e.g., an [enum]
-derived type) or where no custom completion logic is specified (such as via [ArgumentCompleterAttribute()]
or [ValidateSet()]
attributes).
However:
It is questionable whether it makes sense to assume that all such parameters by default support wildcard expressions, except for the seemingly hard-coded -LiteralPath
exception.
Ideally, wildcard support should solely be inferred from the presence of a [SupportsWildcards()]
on the target parameter - see GitHub suggestion #14149
Presumably, the reason that wildcard support is assumed by default is that the file-processing cmdlets' first positional parameter is the wildcard-based -Path
parameter (e.g. Get-ChildItem *.txt
), for which escaping of verbatim [
and ]
is needed.
While the escaping makes it easier to pass arguments to such cmdlets positionally, this also means that if an argument is not obtained by tab-completion - such as by typing or pasting the name - escaping must be performed too, which may be unexpected.
Conversely, code that expects unescaped paths and uses -LiteralPath
with file-processing cmdlets breaks with an escaped path resulting from tab completion.
It is pointless to perform file-name completion on type-constrained parameters whose type is something other than [string]
or [System.IO.FileInfo]
/ [System.IO.DirectoryInfo]
(or arrays thereof).
- For instance,
function foo { param([int] $i) }
currently unexpectedly also tab-completes file names, even though that makes no sense (and breaks the call, if it is attempted).