Last active
November 5, 2019 07:28
-
-
Save msftrncs/bff8c6c5e28ff92a19efb8a5556a4238 to your computer and use it in GitHub Desktop.
PowerShell (v6.2+) filter to quote arguments in order to use them in command line completions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using namespace System.Management.Automation.Language | |
| function CmdRequiresQuote ([string]$in) { | |
| [QuoteCheck]::CmdRequiresQuote($in, $false) | |
| } | |
| function CmdRequiresQuote ([string]$in, [bool]$IsExpandable = $false) { | |
| [Token[]]$_tokens = $null | |
| (_CommonRequiresQuote $in $IsExpandable ([ref]$_tokens)) -or | |
| -not $IsExpandable -and ($_tokens[0].Kind -in ( | |
| [TokenKind]::Number, [TokenKind]::Semi) -or | |
| $_tokens[0].TokenFlags -band ([TokenFlags]::UnaryOperator -bor [TokenFlags]::Keyword)) | |
| } | |
| function ArgRequiresQuote ([string]$in) { | |
| [Token[]]$_tokens = $null | |
| (_CommonRequiresQuote $in $true ([ref]$_tokens)) -or $_tokens[1].Kind -in ( | |
| [TokenKind]::Redirection, | |
| [TokenKind]::RedirectInStd, | |
| [TokenKind]::Parameter) | |
| } | |
| function _CommonRequiresQuote([string]$in, [bool]$IsExpandable, [ref]$_tokens_ref) { | |
| [ParseError[]]$_parseerrors = $null | |
| $tokenToCheck = if ($IsExpandable) { 1 } else { 0 } | |
| [Parser]::ParseInput("$(if ($IsExpandable) {'&'})$in", $_tokens_ref, [ref]$_parseerrors) | Out-Null | |
| $_tokens = $_tokens_ref.Value | |
| $_parseerrors.Length -ne 0 -or $_tokens.Length -ne ($tokenToCheck + 2) -or $_tokens[$tokenToCheck].Kind -in ( | |
| [TokenKind]::Variable, | |
| [TokenKind]::SplattedVariable, | |
| [TokenKind]::StringExpandable, | |
| [TokenKind]::StringLiteral, | |
| [TokenKind]::HereStringExpandable, | |
| [TokenKind]::HereStringLiteral, | |
| [TokenKind]::Comment) -or ($IsExpandable -and $_tokens[1] -is [StringExpandableToken]) -or | |
| ($_tokens[$tokenToCheck] -is [StringToken] -and $_tokens[$tokenToCheck].Value.Length -ne $in.Length) -or | |
| $_tokens[$tokenToCheck + 1].Kind -ne [TokenKind]::EndOfInput | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| filter quoteArgWithSpecChars { | |
| param( | |
| [ValidateSet([char]0us, [char]34us, [char]39us, [char]0x2018us, [char]0x2019us, [char]0x201Aus, [char]0x201Bus, [char]0x201Cus, [char]0x201Dus, [char]0x201Eus)] | |
| [char]$QuotedWith = [char]0us, # specifies quote character argument was previously quoted with | |
| [bool]$IsLiteralPath = $true, # specifies argument is a literal and needs no wildcard escaping | |
| [string]$PrefixText = '' # portion of argument that has already been completed, in its raw (unescaped) form | |
| ) | |
| # filter a list of potential command argument completions, altering them for compatibility with PowerShell's tokenizer | |
| # return a hash table of the original item (ListItemText) and the completion text that would be inserted | |
| # this resembles the System.Management.Automation.CompletionResult class | |
| [pscustomobject]@{ | |
| ListItemText = $_ | |
| CompletionText = "$($( | |
| # first, force to a literal if argument isn't automatically literal | |
| if (-not $IsLiteralPath) { | |
| # must escape certain wildcard patterns | |
| # kludge, WildcardPattern.Escape doesn't escape the escape character | |
| [WildcardPattern]::Escape("$PrefixText$_".Replace('`','``')) | |
| } else { | |
| "$PrefixText$_" | |
| } | |
| ).foreach{ | |
| # escape according to type of quoting completion will use | |
| if ($QuotedWith -eq [char]0us) { | |
| # bareword, check if completion must be forced to be quoted | |
| if ($_ -match '^(?:[@#<>]|[1-6]>|[-\u2013-\u2015](?:[-\u2013-\u2015]$|[_\p{L}]))|[\s`|&;,''"\u2018-\u201E{}()]|\$[{(\w:$^?]') { #) | |
| # needs to be single-quoted | |
| "'$($_ -replace '[''\u2018-\u201B]', '$0$0')'" | |
| } else { | |
| # is fine as is | |
| $_ | |
| } | |
| } elseif ($QuotedWith -notin [char]34us, [char]0x201Cus, [char]0x201Dus, [char]0x201Eus) { | |
| # single-quoted | |
| "$QuotedWith$($_ -replace '[''\u2018-\u201B]', '$0$0')$QuotedWith" | |
| } else { | |
| # double-quoted | |
| "$QuotedWith$($_ -replace '["\u201C-\u201E`]', '$0$0' -replace '\$(?=[{(\w:$^?])'<#)#>, '`$0')$QuotedWith" | |
| } | |
| })" | |
| } | |
| # see https://github.com/PowerShell/PowerShell/issues/4543 regarding the commented `)`, they are neccessary. | |
| } |
Author
Author
Revised again to include quoting of a bareword argument if it would appear to be a parameter name specification in a completion.
Author
Revised again to kludge [WildCardPattern]::Escape() which fails to escape the escape character.
Author
Added PSRequiresQuotes to demonstrate a method that uses the PowerShell parser and evaluates its results to determine if quoting is needed. This better supports command name quoting.
Author
Corrected PSRequiresQuotes, TokenKind does not include a Keyword, use TokenFlags instead.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Revised again to correct behavior of quoting, escaping
$in bareword or doublequoted arguments.$only needs quoted (bareword) or escaped (doublequoted) when followed by a character that that would justify it making a variable or subexpression reference. This allows this filter to match the behavior of the variable name argument completer, which will complete the$variable with just$since a single$cannot reference a variable and so becomes a literal.