-
-
Save msftrncs/bff8c6c5e28ff92a19efb8a5556a4238 to your computer and use it in GitHub Desktop.
| 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 | |
| } |
| 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. | |
| } |
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.
Revised again to include quoting of a bareword argument if it would appear to be a parameter name specification in a completion.
Revised again to kludge [WildCardPattern]::Escape() which fails to escape the escape character.
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.
Corrected PSRequiresQuotes, TokenKind does not include a Keyword, use TokenFlags instead.
I've revised again to demonstrate an output similar to the System.Management.Automation.CompletionResult class. The filter returns results as a PSObject with two properties,
ListItemTextwhich is the input text before quoting and escaping, andCompletionTextwhich is the complete quoted and escaped text to be completed, using the input parameterPrefixTextas the previously completed text, which is demonstrated in the revised previous comment.