/// <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; }
/// <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); }