mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
- Remove the year about copyright - Fix casing of `All rights reserved` - Replace Unicode characters representing single quote with a single quote
316 lines
13 KiB
C#
316 lines
13 KiB
C#
/********************************************************************++
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
--********************************************************************/
|
|
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Management.Automation.Internal;
|
|
|
|
namespace System.Management.Automation
|
|
{
|
|
/// <summary>
|
|
/// This is the interface between the ScriptCommandProcessor and the
|
|
/// parameter binders required to bind parameters to a shell function.
|
|
/// </summary>
|
|
///
|
|
internal class ScriptParameterBinderController : ParameterBinderController
|
|
{
|
|
#region ctor
|
|
|
|
/// <summary>
|
|
/// Initializes the cmdlet parameter binder controller for
|
|
/// the specified cmdlet and engine context
|
|
/// </summary>
|
|
///
|
|
/// <param name="script">
|
|
/// The script that contains the parameter metadata.
|
|
/// </param>
|
|
/// <param name="invocationInfo">
|
|
/// The invocation information about the code being run.
|
|
/// </param>
|
|
/// <param name="context">
|
|
/// The engine context the cmdlet is run in.
|
|
/// </param>
|
|
/// <param name="command">
|
|
/// The command that the parameters will be bound to.
|
|
/// </param>
|
|
/// <param name="localScope">
|
|
/// The scope that the parameter binder will use to set parameters.
|
|
/// </param>
|
|
internal ScriptParameterBinderController(
|
|
ScriptBlock script,
|
|
InvocationInfo invocationInfo,
|
|
ExecutionContext context,
|
|
InternalCommand command,
|
|
SessionStateScope localScope)
|
|
: base(invocationInfo, context, new ScriptParameterBinder(script, invocationInfo, context, command, localScope))
|
|
{
|
|
this.DollarArgs = new List<object>();
|
|
|
|
// Add the script parameter metadata to the bindable parameters
|
|
// And add them to the unbound parameters list
|
|
|
|
if (script.HasDynamicParameters)
|
|
{
|
|
UnboundParameters = this.BindableParameters.ReplaceMetadata(script.ParameterMetadata);
|
|
}
|
|
else
|
|
{
|
|
_bindableParameters = script.ParameterMetadata;
|
|
UnboundParameters = new List<MergedCompiledCommandParameter>(_bindableParameters.BindableParameters.Values);
|
|
}
|
|
}
|
|
|
|
#endregion ctor
|
|
|
|
/// <summary>
|
|
/// Holds the set of parameters that were not bound to any argument (i.e $args)
|
|
/// </summary>
|
|
internal List<object> DollarArgs { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Binds the command line parameters for shell functions/filters/scripts/scriptblocks
|
|
/// </summary>
|
|
///
|
|
/// <param name="arguments">
|
|
/// The arguments to be bound.
|
|
/// </param>
|
|
///
|
|
/// <returns>
|
|
/// True if binding was successful or false otherwise.
|
|
/// </returns>
|
|
internal void BindCommandLineParameters(Collection<CommandParameterInternal> arguments)
|
|
{
|
|
// Add the passed in arguments to the unboundArguments collection
|
|
|
|
foreach (CommandParameterInternal argument in arguments)
|
|
{
|
|
UnboundArguments.Add(argument);
|
|
}
|
|
|
|
ReparseUnboundArguments();
|
|
|
|
// To support named parameters you just have un-comment the following line
|
|
UnboundArguments = BindParameters(UnboundArguments);
|
|
|
|
ParameterBindingException parameterBindingError;
|
|
UnboundArguments =
|
|
BindPositionalParameters(
|
|
UnboundArguments,
|
|
uint.MaxValue,
|
|
uint.MaxValue,
|
|
out parameterBindingError);
|
|
|
|
try
|
|
{
|
|
this.DefaultParameterBinder.RecordBoundParameters = false;
|
|
|
|
// If there are any unbound parameters that have default values, then
|
|
// set those default values.
|
|
BindUnboundScriptParameters();
|
|
|
|
// If there are any unbound arguments, stick them into $args
|
|
HandleRemainingArguments(UnboundArguments);
|
|
}
|
|
finally
|
|
{
|
|
this.DefaultParameterBinder.RecordBoundParameters = true;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Passes the binding directly through to the parameter binder.
|
|
/// It does no verification against metadata.
|
|
/// </summary>
|
|
///
|
|
/// <param name="argument">
|
|
/// The name and value of the variable to bind.
|
|
/// </param>
|
|
///
|
|
/// <param name="flags">
|
|
/// Ignored.
|
|
/// </param>
|
|
///
|
|
/// <returns>
|
|
/// True if the parameter was successfully bound. Any error condition
|
|
/// produces an exception.
|
|
/// </returns>
|
|
///
|
|
internal override bool BindParameter(CommandParameterInternal argument, ParameterBindingFlags flags)
|
|
{
|
|
// Just pass the binding straight through. No metadata to verify the parameter against.
|
|
DefaultParameterBinder.BindParameter(argument.ParameterName, argument.ArgumentValue, parameterMetadata: null);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Binds the specified parameters to the shell function
|
|
/// </summary>
|
|
///
|
|
/// <param name="arguments">
|
|
/// The arguments to bind.
|
|
/// </param>
|
|
///
|
|
internal override Collection<CommandParameterInternal> BindParameters(Collection<CommandParameterInternal> arguments)
|
|
{
|
|
Collection<CommandParameterInternal> result = new Collection<CommandParameterInternal>();
|
|
|
|
foreach (CommandParameterInternal argument in arguments)
|
|
{
|
|
if (!argument.ParameterNameSpecified)
|
|
{
|
|
result.Add(argument);
|
|
continue;
|
|
}
|
|
|
|
// We don't want to throw an exception yet because
|
|
// the parameter might be a positional argument
|
|
|
|
MergedCompiledCommandParameter parameter =
|
|
BindableParameters.GetMatchingParameter(
|
|
argument.ParameterName,
|
|
false, true,
|
|
new InvocationInfo(this.InvocationInfo.MyCommand, argument.ParameterExtent));
|
|
|
|
// If the parameter is not in the specified parameter set,
|
|
// throw a binding exception
|
|
|
|
if (parameter != null)
|
|
{
|
|
// Now check to make sure it hasn't already been
|
|
// bound by looking in the boundParameters collection
|
|
|
|
if (BoundParameters.ContainsKey(parameter.Parameter.Name))
|
|
{
|
|
ParameterBindingException bindingException =
|
|
new ParameterBindingException(
|
|
ErrorCategory.InvalidArgument,
|
|
this.InvocationInfo,
|
|
GetParameterErrorExtent(argument),
|
|
argument.ParameterName,
|
|
null,
|
|
null,
|
|
ParameterBinderStrings.ParameterAlreadyBound,
|
|
"ParameterAlreadyBound");
|
|
|
|
throw bindingException;
|
|
}
|
|
BindParameter(uint.MaxValue, argument, parameter, ParameterBindingFlags.ShouldCoerceType);
|
|
}
|
|
else if (argument.ParameterName.Equals(Language.Parser.VERBATIM_PARAMETERNAME, StringComparison.Ordinal))
|
|
{
|
|
// We sometimes send a magic parameter from a remote machine with the values referenced via
|
|
// a using expression ($using:x). We then access these values via PSBoundParameters, so
|
|
// "bind" them here.
|
|
DefaultParameterBinder.CommandLineParameters.SetImplicitUsingParameters(argument.ArgumentValue);
|
|
}
|
|
else
|
|
{
|
|
result.Add(argument);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes the remaining arguments that haven't been bound, and binds
|
|
/// them to $args
|
|
/// </summary>
|
|
///
|
|
/// <param name="arguments">
|
|
/// The remaining unbound arguments.
|
|
/// </param>
|
|
///
|
|
/// <remarks>
|
|
/// An array containing the values that were bound to $args.
|
|
/// </remarks>
|
|
private void HandleRemainingArguments(Collection<CommandParameterInternal> arguments)
|
|
{
|
|
List<object> args = new List<object>();
|
|
|
|
foreach (CommandParameterInternal parameter in arguments)
|
|
{
|
|
object argValue = parameter.ArgumentSpecified ? parameter.ArgumentValue : null;
|
|
|
|
// Proper automatic proxy generation requires the ability to prevent unbound arguments
|
|
// in the proxy from binding to positional parameters in the proxied command. We use
|
|
// a special key ("$args") when splatting @CommandLineArguments to package up $args.
|
|
// This special key is not created automatically because it is useful to splat @args,
|
|
// just not in the automatically generated proxy.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// function foo { param($a, $b) $a; $b; $args }
|
|
// function foo_proxy { param($a) ; $CommandLineArguments.Add('$args', $args); foo @CommandLineArguments }
|
|
// foo_proxy 1 2 3
|
|
//
|
|
// Then in foo, $a=1, $b=, $args=2,3
|
|
//
|
|
// Here, we want $b in foo to be unbound because the proxy doesn't have $b (an Exchange scenario.)
|
|
// So we pass $args (2,3) in the special entry in @CommandLineArguments. If we had instead written:
|
|
//
|
|
// function foo_proxy { param($a) ; foo @CommandLineArguments @args }
|
|
// foo_proxy 1 2 3
|
|
//
|
|
// Then in foo, $a=1, $b=2, $args=3
|
|
//
|
|
// Note that the name $args is chosen to be:
|
|
// * descriptive
|
|
// * obscure (it can't be a property/field name in C#, and is an unlikely variable in script)
|
|
// So we shouldn't have any real conflict. Note that if someone actually puts ${$args} in their
|
|
// param block, then the value will be bound and we won't have an unbound argument for "$args" here.
|
|
if (parameter.ParameterAndArgumentSpecified &&
|
|
parameter.ParameterName.Equals("$args", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// $args is normally an object[], but because this feature is accessible from script, it's possible
|
|
// for it to contain anything.
|
|
if (argValue is object[])
|
|
{
|
|
args.AddRange(argValue as object[]);
|
|
}
|
|
else
|
|
{
|
|
args.Add(argValue);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (parameter.ParameterNameSpecified)
|
|
{
|
|
// Add a property to the string so we can tell the difference between:
|
|
// foo -abc
|
|
// foo "-abc"
|
|
// This is important when splatting, we reconstruct the parameter if the
|
|
// value is splatted.
|
|
var parameterText = new PSObject(new String(parameter.ParameterText.ToCharArray()));
|
|
if (parameterText.Properties[NotePropertyNameForSplattingParametersInArgs] == null)
|
|
{
|
|
var noteProperty = new PSNoteProperty(NotePropertyNameForSplattingParametersInArgs,
|
|
parameter.ParameterName)
|
|
{ IsHidden = true };
|
|
parameterText.Properties.Add(noteProperty);
|
|
}
|
|
args.Add(parameterText);
|
|
}
|
|
|
|
if (parameter.ArgumentSpecified)
|
|
{
|
|
args.Add(argValue);
|
|
}
|
|
}
|
|
|
|
object[] argsArray = args.ToArray();
|
|
|
|
DefaultParameterBinder.BindParameter(SpecialVariables.Args, argsArray, parameterMetadata: null);
|
|
|
|
DollarArgs.AddRange(argsArray);
|
|
|
|
return;
|
|
}
|
|
|
|
internal const string NotePropertyNameForSplattingParametersInArgs = "<CommandParameterName>";
|
|
}
|
|
}
|