2020-03-24 11:08:37 -07:00
// Copyright (c) Microsoft Corporation.
2018-02-13 09:23:53 -08:00
// Licensed under the MIT License.
2016-03-30 14:20:52 -07:00
using System.Collections ;
2021-04-01 14:54:10 -07:00
using System.Collections.Generic ;
2016-03-30 14:20:52 -07:00
using System.Collections.ObjectModel ;
2017-10-21 17:25:31 -07:00
using System.IO ;
using System.Linq ;
2016-03-30 14:20:52 -07:00
using System.Management.Automation.Internal ;
using System.Text ;
2019-04-30 10:25:11 +05:00
using Microsoft.PowerShell.Commands ;
2016-03-30 14:20:52 -07:00
namespace System.Management.Automation
{
2017-10-21 17:25:31 -07:00
using Language ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// The parameter binder for native commands.
/// </summary>
internal class NativeCommandParameterBinder : ParameterBinderBase
{
#region ctor
/// <summary>
2019-01-06 04:40:24 +05:00
/// Constructs a NativeCommandParameterBinder.
2016-03-30 14:20:52 -07:00
/// </summary>
/// <param name="command">
/// The NativeCommand to bind to.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="command"/>.Context is null
/// </exception>
internal NativeCommandParameterBinder (
NativeCommand command ) : base ( command . MyInvocation , command . Context , command )
{
2016-07-29 13:02:49 -07:00
_nativeCommand = command ;
2016-03-30 14:20:52 -07:00
}
#endregion ctor
#region internal members
#region Parameter binding
/// <summary>
/// Binds a parameter for a native command (application).
/// </summary>
/// <param name="name">
/// The name of the parameter to bind the value to. For applications
/// this just becomes another parameter...
/// </param>
/// <param name="value">
/// The value to bind to the parameter. It should be assumed by
/// derived classes that the proper type coercion has already taken
/// place and that any prerequisite metadata has been satisfied.
/// </param>
/// <param name="parameterMetadata"></param>
internal override void BindParameter ( string name , object value , CompiledCommandParameter parameterMetadata )
{
Diagnostics . Assert ( false , "Unreachable code" );
throw new NotSupportedException ();
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
internal override object GetDefaultParameterValue ( string name )
{
return null ;
}
internal void BindParameters ( Collection < CommandParameterInternal > parameters )
{
bool sawVerbatimArgumentMarker = false ;
bool first = true ;
foreach ( CommandParameterInternal parameter in parameters )
{
if (! first )
{
2016-07-29 13:02:49 -07:00
_arguments . Append ( ' ' );
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
first = false ;
if ( parameter . ParameterNameSpecified )
{
2020-03-18 18:38:55 +05:00
Diagnostics . Assert (! parameter . ParameterText . Contains ( ' ' ), "Parameters cannot have whitespace" );
2022-07-18 17:25:46 -07:00
PossiblyGlobArg ( parameter . ParameterText , parameter , usedQuotes : false );
2016-03-30 14:20:52 -07:00
if ( parameter . SpaceAfterParameter )
{
2016-07-29 13:02:49 -07:00
_arguments . Append ( ' ' );
2016-03-30 14:20:52 -07:00
}
}
if ( parameter . ArgumentSpecified )
{
// If this is the verbatim argument marker, we don't pass it on to the native command.
// We do need to remember it though - we'll expand environment variables in subsequent args.
object argValue = parameter . ArgumentValue ;
if ( string . Equals ( "--%" , argValue as string , StringComparison . OrdinalIgnoreCase ))
{
sawVerbatimArgumentMarker = true ;
continue ;
}
if ( argValue != AutomationNull . Value && argValue != UnboundParameter . Value )
{
2017-11-06 17:05:26 -08:00
// ArrayLiteralAst is used to reconstruct the correct argument, e.g.
2016-03-30 14:20:52 -07:00
// windbg -k com:port=\\devbox\pipe\debug,pipe,resets=0,reconnect
// The parser produced an array of strings but marked the parameter so we
// can properly reconstruct the correct command line.
2022-07-18 17:25:46 -07:00
bool usedQuotes = false ;
2017-11-06 17:05:26 -08:00
ArrayLiteralAst arrayLiteralAst = null ;
switch ( parameter ?. ArgumentAst )
2017-10-21 17:25:31 -07:00
{
2019-04-24 19:27:22 +05:00
case StringConstantExpressionAst sce :
2022-07-18 17:25:46 -07:00
usedQuotes = sce . StringConstantType != StringConstantType . BareWord ;
2019-04-24 19:27:22 +05:00
break ;
case ExpandableStringExpressionAst ese :
2022-07-18 17:25:46 -07:00
usedQuotes = ese . StringConstantType != StringConstantType . BareWord ;
2019-04-24 19:27:22 +05:00
break ;
case ArrayLiteralAst ala :
arrayLiteralAst = ala ;
break ;
2017-10-21 17:25:31 -07:00
}
2017-11-06 17:05:26 -08:00
2022-07-18 17:25:46 -07:00
AppendOneNativeArgument ( Context , parameter , argValue , arrayLiteralAst , sawVerbatimArgumentMarker , usedQuotes );
2016-03-30 14:20:52 -07:00
}
}
}
}
#endregion Parameter binding
/// <summary>
2019-01-06 04:40:24 +05:00
/// Gets the command arguments in string form.
2016-03-30 14:20:52 -07:00
/// </summary>
2018-12-22 01:26:06 -08:00
internal string Arguments
2016-03-30 14:20:52 -07:00
{
get
{
2016-07-29 13:02:49 -07:00
return _arguments . ToString ();
2016-03-30 14:20:52 -07:00
}
2018-12-08 18:53:30 +02:00
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
private readonly StringBuilder _arguments = new StringBuilder ();
2021-04-01 14:54:10 -07:00
internal string [] ArgumentList
{
get
{
return _argumentList . ToArray ();
}
}
/// <summary>
/// Add an argument to the ArgumentList.
/// We may need to construct the argument out of the parameter text and the argument
/// in the case that we have a parameter that appears as "-switch:value".
/// </summary>
/// <param name="parameter">The parameter associated with the operation.</param>
/// <param name="argument">The value used with parameter.</param>
internal void AddToArgumentList ( CommandParameterInternal parameter , string argument )
{
if ( parameter . ParameterNameSpecified && parameter . ParameterText . EndsWith ( ":" ))
{
if ( argument != parameter . ParameterText )
{
2022-07-18 17:51:51 -07:00
// Only combine the text and argument if there was no space after the parameter,
// otherwise, add the parameter and arguments as separate elements.
if ( parameter . SpaceAfterParameter )
{
_argumentList . Add ( parameter . ParameterText );
_argumentList . Add ( argument );
}
else
{
_argumentList . Add ( parameter . ParameterText + argument );
}
2021-04-01 14:54:10 -07:00
}
}
else
{
_argumentList . Add ( argument );
}
}
2021-07-07 05:33:19 +01:00
private readonly List < string > _argumentList = new List < string >();
2021-04-01 14:54:10 -07:00
/// <summary>
/// Gets a value indicating whether to use an ArgumentList or string for arguments when invoking a native executable.
/// </summary>
2021-07-20 16:10:04 -07:00
internal NativeArgumentPassingStyle ArgumentPassingStyle
2021-04-01 14:54:10 -07:00
{
get
{
2022-09-12 09:48:09 -07:00
try
2021-04-01 14:54:10 -07:00
{
2022-09-12 09:48:09 -07:00
var preference = LanguagePrimitives . ConvertTo < NativeArgumentPassingStyle >(
Context . GetVariableValue ( SpecialVariables . NativeArgumentPassingVarPath , NativeArgumentPassingStyle . Standard ));
return preference ;
}
catch
{
2023-02-02 23:40:43 -06:00
// The value is not convertible send back Legacy
2022-09-12 09:48:09 -07:00
return NativeArgumentPassingStyle . Legacy ;
2021-04-01 14:54:10 -07:00
}
}
}
2016-03-30 14:20:52 -07:00
#endregion internal members
#region private members
/// <summary>
/// Stringize a non-IEnum argument to a native command, adding quotes
/// and trailing spaces as appropriate. An array gets added as multiple arguments
/// each of which will be stringized.
/// </summary>
2018-12-28 16:39:06 +05:00
/// <param name="context">Execution context instance.</param>
2021-04-01 14:54:10 -07:00
/// <param name="parameter">The parameter associated with the operation.</param>
2018-12-28 16:39:06 +05:00
/// <param name="obj">The object to append.</param>
/// <param name="argArrayAst">If the argument was an array literal, the Ast, otherwise null.</param>
2018-12-29 08:25:49 +05:00
/// <param name="sawVerbatimArgumentMarker">True if the argument occurs after --%.</param>
2022-07-18 17:25:46 -07:00
/// <param name="usedQuotes">True if the argument was a quoted string (single or double).</param>
private void AppendOneNativeArgument ( ExecutionContext context , CommandParameterInternal parameter , object obj , ArrayLiteralAst argArrayAst , bool sawVerbatimArgumentMarker , bool usedQuotes )
2016-03-30 14:20:52 -07:00
{
IEnumerator list = LanguagePrimitives . GetEnumerator ( obj );
2021-04-01 14:54:10 -07:00
Diagnostics . Assert (( argArrayAst == null ) || ( obj is object [] && (( object []) obj ). Length == argArrayAst . Elements . Count ), "array argument and ArrayLiteralAst differ in number of elements" );
2017-11-06 17:05:26 -08:00
int currentElement = - 1 ;
2018-06-13 00:30:10 +05:00
string separator = string . Empty ;
2016-03-30 14:20:52 -07:00
do
{
string arg ;
2021-04-01 14:54:10 -07:00
object currentObj ;
2020-07-30 16:06:38 -07:00
if ( list == null )
2016-03-30 14:20:52 -07:00
{
arg = PSObject . ToStringParser ( context , obj );
2021-04-01 14:54:10 -07:00
currentObj = obj ;
2016-03-30 14:20:52 -07:00
}
else
{
if (! ParserOps . MoveNext ( context , null , list ))
{
break ;
}
2018-12-23 22:20:06 -08:00
2021-04-01 14:54:10 -07:00
currentObj = ParserOps . Current ( null , list );
arg = PSObject . ToStringParser ( context , currentObj );
2017-11-06 17:05:26 -08:00
currentElement += 1 ;
if ( currentElement != 0 )
{
separator = GetEnumerableArgSeparator ( argArrayAst , currentElement );
}
2016-03-30 14:20:52 -07:00
}
2018-12-31 15:10:15 +05:00
if (! string . IsNullOrEmpty ( arg ))
2016-03-30 14:20:52 -07:00
{
2021-04-01 14:54:10 -07:00
// Only add the separator to the argument string rather than adding a separator to the ArgumentList.
2017-11-06 17:05:26 -08:00
_arguments . Append ( separator );
2016-03-30 14:20:52 -07:00
if ( sawVerbatimArgumentMarker )
{
arg = Environment . ExpandEnvironmentVariables ( arg );
2016-07-29 13:02:49 -07:00
_arguments . Append ( arg );
2021-04-01 14:54:10 -07:00
// we need to split the argument on spaces
_argumentList . AddRange ( arg . Split ( ' ' , StringSplitOptions . RemoveEmptyEntries ));
2016-03-30 14:20:52 -07:00
}
else
{
// We need to add quotes if the argument has unquoted spaces. The
// quotes could appear anywhere inside the string, not just at the start,
// e.g.
// $a = 'a"b c"d'
// echoargs $a 'a"b c"d' a"b c"d
//
// The above should see 3 identical arguments in argv (the command line will
// actually have quotes in different places, but the Win32 command line=>argv parser
// erases those differences.
//
2017-01-16 13:31:14 -08:00
// We need to check quotes that the win32 argument parser checks which is currently
2016-03-30 14:20:52 -07:00
// just the normal double quotes, no other special quotes. Also note that mismatched
2017-05-01 12:23:36 -07:00
// quotes are supported
if ( NeedQuotes ( arg ))
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
_arguments . Append ( '"' );
2022-07-18 17:25:46 -07:00
AddToArgumentList ( parameter , arg );
2020-04-27 14:14:57 -07:00
2017-10-11 07:01:09 -07:00
// need to escape all trailing backslashes so the native command receives it correctly
// according to http://www.daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESDOC
2022-07-18 17:25:46 -07:00
_arguments . Append ( arg );
2019-04-24 19:27:22 +05:00
for ( int i = arg . Length - 1 ; i >= 0 && arg [ i ] == '\\' ; i --)
2017-10-11 07:01:09 -07:00
{
_arguments . Append ( '\\' );
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
_arguments . Append ( '"' );
2016-03-30 14:20:52 -07:00
}
else
{
2022-01-11 23:21:32 -08:00
if ( argArrayAst != null && ArgumentPassingStyle != NativeArgumentPassingStyle . Legacy )
2021-04-01 14:54:10 -07:00
{
// We have a literal array, so take the extent, break it on spaces and add them to the argument list.
foreach ( string element in argArrayAst . Extent . Text . Split ( ' ' , StringSplitOptions . RemoveEmptyEntries ))
{
2022-07-18 17:25:46 -07:00
PossiblyGlobArg ( element , parameter , usedQuotes );
2021-04-01 14:54:10 -07:00
}
break ;
}
else
{
2022-07-18 17:25:46 -07:00
PossiblyGlobArg ( arg , parameter , usedQuotes );
2021-04-01 14:54:10 -07:00
}
2016-03-30 14:20:52 -07:00
}
}
}
2022-01-11 23:21:32 -08:00
else if ( ArgumentPassingStyle != NativeArgumentPassingStyle . Legacy && currentObj != null )
2021-04-01 14:54:10 -07:00
{
// add empty strings to arglist, but not nulls
AddToArgumentList ( parameter , arg );
}
2020-04-27 14:14:57 -07:00
}
while ( list != null );
2016-03-30 14:20:52 -07:00
}
2017-10-21 17:25:31 -07:00
/// <summary>
/// On Windows, just append <paramref name="arg"/>.
/// On Unix, do globbing as appropriate, otherwise just append <paramref name="arg"/>.
/// </summary>
2018-12-28 16:39:06 +05:00
/// <param name="arg">The argument that possibly needs expansion.</param>
2021-04-01 14:54:10 -07:00
/// <param name="parameter">The parameter associated with the operation.</param>
2022-07-18 17:25:46 -07:00
/// <param name="usedQuotes">True if the argument was a quoted string (single or double).</param>
private void PossiblyGlobArg ( string arg , CommandParameterInternal parameter , bool usedQuotes )
2017-10-21 17:25:31 -07:00
{
var argExpanded = false ;
#if UNIX
// On UNIX systems, we expand arguments containing wildcard expressions against
// the file system just like bash, etc.
2022-07-18 17:25:46 -07:00
if (! usedQuotes && WildcardPattern . ContainsWildcardCharacters ( arg ))
2017-10-21 17:25:31 -07:00
{
2022-07-18 17:25:46 -07:00
// See if the current working directory is a filesystem provider location
// We won't do the expansion if it isn't since native commands can only access the file system.
var cwdinfo = Context . EngineSessionState . CurrentLocation ;
// If it's a filesystem location then expand the wildcards
if ( cwdinfo . Provider . Name . Equals ( FileSystemProvider . ProviderName , StringComparison . OrdinalIgnoreCase ))
2017-10-21 17:25:31 -07:00
{
2022-07-18 17:25:46 -07:00
// On UNIX, paths starting with ~ or absolute paths are not normalized
bool normalizePath = arg . Length == 0 || !( arg [ 0 ] == '~' || arg [ 0 ] == '/' );
2017-10-21 17:25:31 -07:00
2022-07-18 17:25:46 -07:00
// See if there are any matching paths otherwise just add the pattern as the argument
Collection < PSObject > paths = null ;
try
2017-10-21 17:25:31 -07:00
{
2022-07-18 17:25:46 -07:00
paths = Context . EngineSessionState . InvokeProvider . ChildItem . Get ( arg , false );
}
catch
{
// Fallthrough will append the pattern unchanged.
}
2017-10-21 17:25:31 -07:00
2022-07-18 17:25:46 -07:00
// Expand paths, but only from the file system.
if ( paths ?. Count > 0 && paths . All ( p => p . BaseObject is FileSystemInfo ))
{
var sep = string . Empty ;
foreach ( var path in paths )
2017-10-21 17:25:31 -07:00
{
2022-07-18 17:25:46 -07:00
_arguments . Append ( sep );
sep = " " ;
var expandedPath = ( path . BaseObject as FileSystemInfo ). FullName ;
if ( normalizePath )
2020-04-27 14:14:57 -07:00
{
2022-07-18 17:25:46 -07:00
expandedPath =
Context . SessionState . Path . NormalizeRelativePath ( expandedPath , cwdinfo . ProviderPath );
2020-04-27 14:14:57 -07:00
}
2022-07-18 17:25:46 -07:00
// If the path contains spaces, then add quotes around it.
if ( NeedQuotes ( expandedPath ))
{
_arguments . Append ( '"' );
_arguments . Append ( expandedPath );
_arguments . Append ( '"' );
}
else
{
_arguments . Append ( expandedPath );
}
AddToArgumentList ( parameter , expandedPath );
argExpanded = true ;
2017-10-21 17:25:31 -07:00
}
}
}
2022-07-18 17:25:46 -07:00
}
else if (! usedQuotes )
{
// Even if there are no wildcards, we still need to possibly
// expand ~ into the filesystem provider home directory path
ProviderInfo fileSystemProvider = Context . EngineSessionState . GetSingleProvider ( FileSystemProvider . ProviderName );
string home = fileSystemProvider . Home ;
if ( string . Equals ( arg , "~" ))
2017-10-21 17:25:31 -07:00
{
2022-07-18 17:25:46 -07:00
_arguments . Append ( home );
AddToArgumentList ( parameter , home );
argExpanded = true ;
}
else if ( arg . StartsWith ( "~/" , StringComparison . OrdinalIgnoreCase ))
{
var replacementString = string . Concat ( home , arg . AsSpan ( 1 ));
_arguments . Append ( replacementString );
AddToArgumentList ( parameter , replacementString );
argExpanded = true ;
2017-10-21 17:25:31 -07:00
}
}
#endif // UNIX
if (! argExpanded )
{
_arguments . Append ( arg );
2021-04-01 14:54:10 -07:00
AddToArgumentList ( parameter , arg );
2017-10-21 17:25:31 -07:00
}
}
2017-05-01 12:23:36 -07:00
/// <summary>
/// Check to see if the string contains spaces and therefore must be quoted.
/// </summary>
2018-12-28 16:39:06 +05:00
/// <param name="stringToCheck">The string to check for spaces.</param>
2017-05-24 13:30:54 -07:00
internal static bool NeedQuotes ( string stringToCheck )
2017-05-01 12:23:36 -07:00
{
bool needQuotes = false , followingBackslash = false ;
int quoteCount = 0 ;
for ( int i = 0 ; i < stringToCheck . Length ; i ++)
{
if ( stringToCheck [ i ] == '"' && ! followingBackslash )
{
quoteCount += 1 ;
}
else if ( char . IsWhiteSpace ( stringToCheck [ i ]) && ( quoteCount % 2 == 0 ))
{
needQuotes = true ;
}
2018-12-23 22:20:06 -08:00
2017-05-01 12:23:36 -07:00
followingBackslash = stringToCheck [ i ] == '\\' ;
}
2018-12-23 22:20:06 -08:00
2017-05-01 12:23:36 -07:00
return needQuotes ;
}
2018-12-20 22:56:05 -08:00
private static string GetEnumerableArgSeparator ( ArrayLiteralAst arrayLiteralAst , int index )
2017-11-06 17:05:26 -08:00
{
2020-07-30 16:06:38 -07:00
if ( arrayLiteralAst == null ) return " " ;
2017-11-06 17:05:26 -08:00
// index points to the *next* element, so we're looking for space between
// it and the previous element.
var next = arrayLiteralAst . Elements [ index ];
var prev = arrayLiteralAst . Elements [ index - 1 ];
var arrayExtent = arrayLiteralAst . Extent ;
var afterPrev = prev . Extent . EndOffset ;
var beforeNext = next . Extent . StartOffset - 1 ;
if ( afterPrev == beforeNext ) return "," ;
var arrayText = arrayExtent . Text ;
afterPrev -= arrayExtent . StartOffset ;
beforeNext -= arrayExtent . StartOffset ;
if ( arrayText [ afterPrev ] == ',' ) return ", " ;
if ( arrayText [ beforeNext ] == ',' ) return " ," ;
return " , " ;
}
2017-05-01 12:23:36 -07:00
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// The native command to bind to.
2016-03-30 14:20:52 -07:00
/// </summary>
2020-11-05 03:36:23 +00:00
private readonly NativeCommand _nativeCommand ;
2020-04-27 14:14:57 -07:00
2019-04-24 19:27:22 +05:00
#endregion private members
2016-03-30 14:20:52 -07:00
}
2018-12-08 18:53:30 +02:00
}