Merged PR 23331: Remove TabExpansion from remote session configuration (#18795)

This commit is contained in:
Travis Plunk
2022-12-14 19:46:47 +00:00
committed by GitHub
parent de416dc831
commit bea1c6ee0e
2 changed files with 3 additions and 249 deletions
@@ -125,234 +125,6 @@ namespace System.Management.Automation
// to client to let client know if the negotiation succeeded.
internal const string IsNegotiationSucceeded = "IsNegotiationSucceeded";
#region "PSv2 Tab Expansion Function"
internal const string PSv2TabExpansionFunction = "TabExpansion";
/// <summary>
/// This is the PSv2 function for tab expansion. It's only for legacy purpose - used in
/// an interactive remote session from a win7 machine to a win8 machine (or later).
/// </summary>
internal const string PSv2TabExpansionFunctionText = @"
param($line, $lastWord)
& {
function Write-Members ($sep='.')
{
Invoke-Expression ('$_val=' + $_expression)
$_method = [Management.Automation.PSMemberTypes] `
'Method,CodeMethod,ScriptMethod,ParameterizedProperty'
if ($sep -eq '.')
{
$params = @{view = 'extended','adapted','base'}
}
else
{
$params = @{static=$true}
}
foreach ($_m in ,$_val | Get-Member @params $_pat |
Sort-Object membertype,name)
{
if ($_m.MemberType -band $_method)
{
# Return a method...
$_base + $_expression + $sep + $_m.name + '('
}
else {
# Return a property...
$_base + $_expression + $sep + $_m.name
}
}
}
# If a command name contains any of these chars, it needs to be quoted
$_charsRequiringQuotes = ('`&@''#{}()$,;|<> ' + ""`t"").ToCharArray()
# If a variable name contains any of these characters it needs to be in braces
$_varsRequiringQuotes = ('-`&@''#{}()$,;|<> .\/' + ""`t"").ToCharArray()
switch -regex ($lastWord)
{
# Handle property and method expansion rooted at variables...
# e.g. $a.b.<tab>
'(^.*)(\$(\w|:|\.)+)\.([*\w]*)$' {
$_base = $matches[1]
$_expression = $matches[2]
$_pat = $matches[4] + '*'
Write-Members
break;
}
# Handle simple property and method expansion on static members...
# e.g. [datetime]::n<tab>
'(^.*)(\[(\w|\.|\+)+\])(\:\:|\.){0,1}([*\w]*)$' {
$_base = $matches[1]
$_expression = $matches[2]
$_pat = $matches[5] + '*'
Write-Members $(if (! $matches[4]) {'::'} else {$matches[4]})
break;
}
# Handle complex property and method expansion on static members
# where there are intermediate properties...
# e.g. [datetime]::now.d<tab>
'(^.*)(\[(\w|\.|\+)+\](\:\:|\.)(\w+\.)+)([*\w]*)$' {
$_base = $matches[1] # everything before the expression
$_expression = $matches[2].TrimEnd('.') # expression less trailing '.'
$_pat = $matches[6] + '*' # the member to look for...
Write-Members
break;
}
# Handle variable name expansion...
'(^.*\$)([*\w:]+)$' {
$_prefix = $matches[1]
$_varName = $matches[2]
$_colonPos = $_varname.IndexOf(':')
if ($_colonPos -eq -1)
{
$_varName = 'variable:' + $_varName
$_provider = ''
}
else
{
$_provider = $_varname.Substring(0, $_colonPos+1)
}
foreach ($_v in Get-ChildItem ($_varName + '*') | sort Name)
{
$_nameFound = $_v.name
$(if ($_nameFound.IndexOfAny($_varsRequiringQuotes) -eq -1) {'{0}{1}{2}'}
else {'{0}{{{1}{2}}}'}) -f $_prefix, $_provider, $_nameFound
}
break;
}
# Do completion on parameters...
'^-([*\w0-9]*)' {
$_pat = $matches[1] + '*'
# extract the command name from the string
# first split the string into statements and pipeline elements
# This doesn't handle strings however.
$_command = [regex]::Split($line, '[|;=]')[-1]
# Extract the trailing unclosed block e.g. ls | foreach { cp
if ($_command -match '\{([^\{\}]*)$')
{
$_command = $matches[1]
}
# Extract the longest unclosed parenthetical expression...
if ($_command -match '\(([^()]*)$')
{
$_command = $matches[1]
}
# take the first space separated token of the remaining string
# as the command to look up. Trim any leading or trailing spaces
# so you don't get leading empty elements.
$_command = $_command.TrimEnd('-')
$_command,$_arguments = $_command.Trim().Split()
# now get the info object for it, -ArgumentList will force aliases to be resolved
# it also retrieves dynamic parameters
try
{
$_command = @(Get-Command -type 'Alias,Cmdlet,Function,Filter,ExternalScript' `
-Name $_command -ArgumentList $_arguments)[0]
}
catch
{
# see if the command is an alias. If so, resolve it to the real command
if(Test-Path alias:\$_command)
{
$_command = @(Get-Command -Type Alias $_command)[0].Definition
}
# If we were unsuccessful retrieving the command, try again without the parameters
$_command = @(Get-Command -type 'Cmdlet,Function,Filter,ExternalScript' `
-Name $_command)[0]
}
# remove errors generated by the command not being found, and break
if(-not $_command) { $error.RemoveAt(0); break; }
# expand the parameter sets and emit the matching elements
# need to use psbase.Keys in case 'keys' is one of the parameters
# to the cmdlet
foreach ($_n in $_command.Parameters.psbase.Keys)
{
if ($_n -like $_pat) { '-' + $_n }
}
break;
}
# Tab complete against history either #<pattern> or #<id>
'^#(\w*)' {
$_pattern = $matches[1]
if ($_pattern -match '^[0-9]+$')
{
Get-History -ea SilentlyContinue -Id $_pattern | ForEach-Object { $_.CommandLine }
}
else
{
$_pattern = '*' + $_pattern + '*'
Get-History -Count 32767 | Sort-Object -Descending Id| ForEach-Object { $_.CommandLine } | where { $_ -like $_pattern }
}
break;
}
# try to find a matching command...
default {
# parse the script...
$_tokens = [System.Management.Automation.PSParser]::Tokenize($line,
[ref] $null)
if ($_tokens)
{
$_lastToken = $_tokens[$_tokens.count - 1]
if ($_lastToken.Type -eq 'Command')
{
$_cmd = $_lastToken.Content
# don't look for paths...
if ($_cmd.IndexOfAny('/\:') -eq -1)
{
# handle parsing errors - the last token string should be the last
# string in the line...
if ($lastword.Length -ge $_cmd.Length -and
$lastword.substring($lastword.length-$_cmd.length) -eq $_cmd)
{
$_pat = $_cmd + '*'
$_base = $lastword.substring(0, $lastword.length-$_cmd.length)
# get files in current directory first, then look for commands...
$( try {Resolve-Path -ea SilentlyContinue -Relative $_pat } catch {} ;
try { $ExecutionContext.InvokeCommand.GetCommandName($_pat, $true, $false) |
Sort-Object -Unique } catch {} ) |
# If the command contains non-word characters (space, ) ] ; ) etc.)
# then it needs to be quoted and prefixed with &
ForEach-Object {
if ($_.IndexOfAny($_charsRequiringQuotes) -eq -1) { $_ }
elseif ($_.IndexOf('''') -ge 0) {'& ''{0}''' -f $_.Replace('''','''''') }
else { '& ''{0}''' -f $_ }} |
ForEach-Object {'{0}{1}' -f $_base,$_ }
}
}
}
}
}
}
}
";
#endregion "PSv2 Tab Expansion Function"
#region Host Related Strings
internal const string CallId = "ci";
@@ -802,32 +802,14 @@ namespace System.Management.Automation.Remoting
RemotingErrorIdStrings.PSSenderInfoDescription),
ScopedItemOptions.ReadOnly));
// check if the current scenario is Win7(client) to Win8(server). Add back the PSv2 version TabExpansion
// function if necessary.
// Get client PS version from PSSenderInfo.
Version psClientVersion = null;
if (_senderInfo.ApplicationArguments != null && _senderInfo.ApplicationArguments.ContainsKey("PSversionTable"))
{
var value = PSObject.Base(_senderInfo.ApplicationArguments["PSversionTable"]) as PSPrimitiveDictionary;
if (value != null)
if (value != null && value.ContainsKey("PSVersion"))
{
if (value.ContainsKey("WSManStackVersion"))
{
var wsmanStackVersion = PSObject.Base(value["WSManStackVersion"]) as Version;
if (wsmanStackVersion != null && wsmanStackVersion.Major < 3)
{
// The client side is PSv2. This is the Win7 to Win8 scenario. We need to add the PSv2
// TabExpansion function back in to keep the tab expansion functionable on the client side.
rsSessionStateToUse.Commands.Add(
new SessionStateFunctionEntry(
RemoteDataNameStrings.PSv2TabExpansionFunction,
RemoteDataNameStrings.PSv2TabExpansionFunctionText));
}
}
if (value.ContainsKey("PSVersion"))
{
psClientVersion = PSObject.Base(value["PSVersion"]) as Version;
}
psClientVersion = PSObject.Base(value["PSVersion"]) as Version;
}
}