-
-
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. | |
| } |
I've revised again, to handle cases of arguments that support wildcard's (usually provider implemented paths), and the standard provider wildcard characters need escaped. Note, that if the type suffixes us are removed from the numeric literals, this filter should be compatible now with most versions of PowerShell prior to 6.2.
Example use:
(dir variable:*).name | quoteArgWithSpecChars $null $false 'variable:'This provides a list of path completions for a 'variable:' starting point, for an argument that is not inherently literal. It uses the dir alias of Get-ChildItem, as it was meant for testing in an interactive environment.
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, ListItemText which is the input text before quoting and escaping, and CompletionText which is the complete quoted and escaped text to be completed, using the input parameter PrefixText as the previously completed text, which is demonstrated in the revised previous 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.
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.
This code is PowerShell v6+ primarily because of the
`u{x}escapes being used. In some places this is easy to remedy, but in others it might cause a high scripting overhead to remove them, as its probably undesirable to use literal constants for the special quote characters.I've made some simplifications, and also added an escape for
$in double-quoted arguments that I previously missed.