MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Generic;5using System.Collections.ObjectModel;6using System.Diagnostics;7using System.Globalization;8using System.Management.Automation.Language;9using System.Text;10 11namespace System.Management.Automation12{13 /// <summary>14 /// The base class for the parameter binder controllers. This class and15 /// its derived classes control the interaction between the command processor16 /// and the parameter binder(s). It holds the state of the arguments and parameters.17 /// </summary>18 [DebuggerDisplay("InvocationInfo = {InvocationInfo}")]19 internal abstract class ParameterBinderController20 {21 #region ctor22 23 /// <summary>24 /// Constructs a parameter binder controller for the specified command25 /// in the specified engine context.26 /// </summary>27 /// <param name="invocationInfo">28 /// The invocation information about the code being run.29 /// </param>30 /// <param name="context">31 /// The engine context in which the command is being run.32 /// </param>33 /// <param name="parameterBinder">34 /// The default parameter binder for the command.35 /// </param>36 internal ParameterBinderController(InvocationInfo invocationInfo, ExecutionContext context, ParameterBinderBase parameterBinder)37 {38 Diagnostics.Assert(invocationInfo != null, "Caller to verify invocationInfo is not null.");39 Diagnostics.Assert(parameterBinder != null, "Caller to verify parameterBinder is not null.");40 Diagnostics.Assert(context != null, "call to verify context is not null.");41 42 this.DefaultParameterBinder = parameterBinder;43 Context = context;44 InvocationInfo = invocationInfo;45 }46 47 #endregion ctor48 49 #region internal_members50 51 /// <summary>52 /// The engine context the command is running in.53 /// </summary>54 internal ExecutionContext Context { get; }55 56 /// <summary>57 /// Gets the parameter binder for the command.58 /// </summary>59 internal ParameterBinderBase DefaultParameterBinder { get; }60 61 /// <summary>62 /// The invocation information about the code being run.63 /// </summary>64 internal InvocationInfo InvocationInfo { get; }65 66 /// <summary>67 /// All the metadata associated with any of the parameters that68 /// are available from the command.69 /// </summary>70 internal MergedCommandParameterMetadata BindableParameters71 {72 get { return _bindableParameters; }73 }74 75 protected MergedCommandParameterMetadata _bindableParameters = new MergedCommandParameterMetadata();76 77 /// <summary>78 /// A list of the unbound parameters for the command.79 /// </summary>80 protected List<MergedCompiledCommandParameter> UnboundParameters { get; set; }81 82 /// <summary>83 /// A collection of the bound parameters for the command. The collection is84 /// indexed based on the name of the parameter.85 /// </summary>86 protected Dictionary<string, MergedCompiledCommandParameter> BoundParameters { get; } = new Dictionary<string, MergedCompiledCommandParameter>(StringComparer.OrdinalIgnoreCase);87 88 internal CommandLineParameters CommandLineParameters89 {90 get { return this.DefaultParameterBinder.CommandLineParameters; }91 }92 93 /// <summary>94 /// Set true if the default parameter binding is in use.95 /// </summary>96 protected bool DefaultParameterBindingInUse { get; set; } = false;97 98 // Set true if the default parameter values are applied99 100 /// <summary>101 /// A collection of bound default parameters.102 /// </summary>103 protected Collection<string> BoundDefaultParameters { get; } = new Collection<string>();104 105 // Keep record of the bound default parameters106 107 /// <summary>108 /// A collection of the unbound arguments.109 /// </summary>110 /// <value></value>111 protected Collection<CommandParameterInternal> UnboundArguments { get; set; } = new Collection<CommandParameterInternal>();112 113 internal void ClearUnboundArguments()114 {115 UnboundArguments.Clear();116 }117 118 /// <summary>119 /// A collection of the arguments that have been bound.120 /// </summary>121 protected Dictionary<string, CommandParameterInternal> BoundArguments { get; } = new Dictionary<string, CommandParameterInternal>(StringComparer.OrdinalIgnoreCase);122 123 /// <summary>124 /// Reparses the unbound arguments using the parameter metadata of the125 /// specified parameter binder as the parsing guide.126 /// </summary>127 /// <exception cref="ParameterBindingException">128 /// If a parameter token is not matched with an argument and its not a bool or129 /// SwitchParameter.130 /// Or131 /// The name of the argument matches more than one parameter.132 /// </exception>133 protected void ReparseUnboundArguments()134 {135 Collection<CommandParameterInternal> result = new Collection<CommandParameterInternal>();136 137 for (int index = 0; index < UnboundArguments.Count; ++index)138 {139 CommandParameterInternal argument = UnboundArguments[index];140 141 // If the parameter name is not specified, or if it is specified _and_ there is an142 // argument, we have nothing to reparse for this argument.143 if (!argument.ParameterNameSpecified || argument.ArgumentSpecified)144 {145 result.Add(argument);146 continue;147 }148 149 Diagnostics.Assert(argument.ParameterNameSpecified && !argument.ArgumentSpecified,150 "At this point, we only process parameters with no arguments");151 152 // Now check the argument name with the binder.153 154 string parameterName = argument.ParameterName;155 MergedCompiledCommandParameter matchingParameter =156 _bindableParameters.GetMatchingParameter(157 parameterName,158 false,159 true,160 new InvocationInfo(this.InvocationInfo.MyCommand, argument.ParameterExtent));161 162 if (matchingParameter == null)163 {164 // Since we couldn't find a match, just add the argument as it was165 // and continue166 result.Add(argument);167 continue;168 }169 170 // Now that we know we have a single match for the parameter name,171 // see if we can figure out what the argument value for the parameter is.172 173 // If its a bool or switch parameter, then set the value to true and continue174 175 if (IsSwitchAndSetValue(parameterName, argument, matchingParameter.Parameter))176 {177 result.Add(argument);178 continue;179 }180 181 // Since it's not a bool or a SwitchParameter we need to check the next182 // argument.183 184 if (UnboundArguments.Count - 1 > index)185 {186 CommandParameterInternal nextArgument = UnboundArguments[index + 1];187 188 // Since the argument appears to be a valid parameter, check the189 // next argument to see if it is the value for that parameter190 191 if (nextArgument.ParameterNameSpecified)192 {193 // Since we have a valid parameter we need to see if the next argument is194 // an argument value for that parameter or a parameter itself.195 196 MergedCompiledCommandParameter nextMatchingParameter =197 _bindableParameters.GetMatchingParameter(198 nextArgument.ParameterName,199 false,200 true,201 new InvocationInfo(this.InvocationInfo.MyCommand, nextArgument.ParameterExtent));202 203 if ((nextMatchingParameter != null) || nextArgument.ParameterAndArgumentSpecified)204 {205 // Since the next argument is a valid parameter that means the current206 // argument doesn't have a value207 208 // It is an error to have an argument that is a parameter name209 // but doesn't have a value210 211 ParameterBindingException exception =212 new ParameterBindingException(213 ErrorCategory.InvalidArgument,214 this.InvocationInfo,215 GetParameterErrorExtent(argument),216 matchingParameter.Parameter.Name,217 matchingParameter.Parameter.Type,218 null,219 ParameterBinderStrings.MissingArgument,220 "MissingArgument");221 222 throw exception;223 }224 225 ++index;226 argument.ParameterName = matchingParameter.Parameter.Name;227 argument.SetArgumentValue(nextArgument.ArgumentAst, nextArgument.ParameterText);228 result.Add(argument);229 continue;230 }231 232 // The next argument appears to be the value for this parameter. Set the value,233 // increment the index and continue234 235 ++index;236 argument.ParameterName = matchingParameter.Parameter.Name;237 argument.SetArgumentValue(nextArgument.ArgumentAst, nextArgument.ArgumentValue);238 result.Add(argument);239 }240 else241 {242 // It is an error to have a argument that is a parameter name243 // but doesn't have a value244 245 ParameterBindingException exception =246 new ParameterBindingException(247 ErrorCategory.InvalidArgument,248 this.InvocationInfo,249 GetParameterErrorExtent(argument),250 matchingParameter.Parameter.Name,251 matchingParameter.Parameter.Type,252 null,253 ParameterBinderStrings.MissingArgument,254 "MissingArgument");255 256 throw exception;257 }258 }259 260 UnboundArguments = result;261 }262 263 protected void InitUnboundArguments(Collection<CommandParameterInternal> arguments)264 {265 // Add the passed in arguments to the unboundArguments collection266 Collection<CommandParameterInternal> paramsFromSplatting = null;267 foreach (CommandParameterInternal argument in arguments)268 {269 if (argument.FromHashtableSplatting)270 {271 paramsFromSplatting ??= new Collection<CommandParameterInternal>();272 paramsFromSplatting.Add(argument);273 }274 else275 {276 UnboundArguments.Add(argument);277 }278 }279 280 // Move the arguments from hashtable splatting to the end of the unbound args list, so that281 // the explicitly specified named arguments can supersede those from a hashtable splatting.282 if (paramsFromSplatting != null)283 {284 foreach (CommandParameterInternal argument in paramsFromSplatting)285 {286 UnboundArguments.Add(argument);287 }288 }289 }290 291 private static bool IsSwitchAndSetValue(292 string argumentName,293 CommandParameterInternal argument,294 CompiledCommandParameter matchingParameter)295 {296 bool result = false;297 298 if (matchingParameter.Type == typeof(SwitchParameter))299 {300 argument.ParameterName = argumentName;301 argument.SetArgumentValue(null, SwitchParameter.Present);302 result = true;303 }304 305 return result;306 }307 308 /// <summary>309 /// The argument looks like a parameter if it is a string310 /// and starts with a dash.311 /// </summary>312 /// <param name="arg">313 /// The argument to check.314 /// </param>315 /// <returns>316 /// True if the argument is a string and starts with a dash,317 /// or false otherwise.318 /// </returns>319 internal static bool ArgumentLooksLikeParameter(string arg)320 {321 bool result = false;322 323 if (!string.IsNullOrEmpty(arg))324 {325 result = arg[0].IsDash();326 }327 328 return result;329 }330 331 /// <summary>332 /// Reparses the arguments specified in the object[] and generates CommandParameterInternal instances333 /// based on whether the arguments look like parameters. The CommandParameterInternal instances then334 /// get added to the specified command processor.335 /// </summary>336 /// <param name="commandProcessor">337 /// The command processor instance to add the reparsed parameters to.338 /// </param>339 /// <param name="arguments">340 /// The arguments that require reparsing.341 /// </param>342 internal static void AddArgumentsToCommandProcessor(CommandProcessorBase commandProcessor, object[] arguments)343 {344 if ((arguments != null) && (arguments.Length > 0))345 {346 PSBoundParametersDictionary boundParameters = arguments[0] as PSBoundParametersDictionary;347 if ((boundParameters != null) && (arguments.Length == 1))348 {349 // If they are supplying a dictionary of parameters, use those directly350 foreach (KeyValuePair<string, object> boundParameter in boundParameters)351 {352 CommandParameterInternal param = CommandParameterInternal.CreateParameterWithArgument(353 /*parameterAst*/null, boundParameter.Key, boundParameter.Key,354 /*argumentAst*/null, boundParameter.Value, false);355 commandProcessor.AddParameter(param);356 }357 }358 else359 {360 // Otherwise, we need to parse them ourselves361 for (int argIndex = 0; argIndex < arguments.Length; ++argIndex)362 {363 CommandParameterInternal param;364 string paramText = arguments[argIndex] as string;365 if (ArgumentLooksLikeParameter(paramText))366 {367 // The argument looks like a parameter.368 // Create a parameter with argument if the paramText is like this: -Path:c:\windows369 // Combine it with the next argument if there is an argument, and the parameter ends in ':'.370 371 var colonIndex = paramText.IndexOf(':');372 if (colonIndex != -1 && colonIndex != paramText.Length - 1)373 {374 param = CommandParameterInternal.CreateParameterWithArgument(375 /*parameterAst*/null, paramText.Substring(1, colonIndex - 1), paramText,376 /*argumentAst*/null, paramText.AsSpan(colonIndex + 1).Trim().ToString(),377 false);378 }379 else if (argIndex == arguments.Length - 1 || paramText[paramText.Length - 1] != ':')380 {381 param = CommandParameterInternal.CreateParameter(382 paramText.Substring(1), paramText);383 }384 else385 {386 param = CommandParameterInternal.CreateParameterWithArgument(387 /*parameterAst*/null, paramText.Substring(1, paramText.Length - 2), paramText,388 /*argumentAst*/null, arguments[argIndex + 1],389 false);390 argIndex++;391 }392 }393 else394 {395 param = CommandParameterInternal.CreateArgument(arguments[argIndex]);396 }397 398 commandProcessor.AddParameter(param);399 }400 }401 }402 }403 404 /// <summary>405 /// Bind the argument to the specified parameter.406 /// </summary>407 /// <param name="argument">408 /// The argument to be bound.409 /// </param>410 /// <param name="flags">411 /// The flags for type coercion, validation, and script block binding.412 /// </param>413 /// <returns>414 /// True if the parameter was successfully bound. False if <paramref name="flags"/> does not have the415 /// flag <see>ParameterBindingFlags.ShouldCoerceType</see> and the type does not match the parameter type.416 /// </returns>417 /// <exception cref="ParameterBindingException">418 /// If argument transformation fails.419 /// or420 /// The argument could not be coerced to the appropriate type for the parameter.421 /// or422 /// The parameter argument transformation, prerequisite, or validation failed.423 /// or424 /// If the binding to the parameter fails.425 /// or426 /// The parameter has already been bound.427 /// </exception>428 internal virtual bool BindParameter(429 CommandParameterInternal argument,430 ParameterBindingFlags flags)431 {432 bool result = false;433 434 MergedCompiledCommandParameter matchingParameter =435 BindableParameters.GetMatchingParameter(436 argument.ParameterName,437 (flags & ParameterBindingFlags.ThrowOnParameterNotFound) != 0,438 true,439 new InvocationInfo(this.InvocationInfo.MyCommand, argument.ParameterExtent));440 441 if (matchingParameter != null)442 {443 // Now check to make sure it hasn't already been444 // bound by looking in the boundParameters collection445 446 if (BoundParameters.ContainsKey(matchingParameter.Parameter.Name))447 {448 ParameterBindingException bindingException =449 new ParameterBindingException(450 ErrorCategory.InvalidArgument,451 this.InvocationInfo,452 GetParameterErrorExtent(argument),453 argument.ParameterName,454 null,455 null,456 ParameterBinderStrings.ParameterAlreadyBound,457 nameof(ParameterBinderStrings.ParameterAlreadyBound));458 459 throw bindingException;460 }461 462 flags &= ~ParameterBindingFlags.DelayBindScriptBlock;463 result = BindParameter(_currentParameterSetFlag, argument, matchingParameter, flags);464 }465 466 return result;467 }468 469 /// <summary>470 /// Derived classes need to define the binding of multiple arguments.471 /// </summary>472 /// <param name="parameters">473 /// The arguments to be bound.474 /// </param>475 /// <returns>476 /// The arguments which are still not bound.477 /// </returns>478 internal virtual Collection<CommandParameterInternal> BindParameters(Collection<CommandParameterInternal> parameters)479 {480 throw new NotImplementedException();481 }482 483 /// <summary>484 /// Bind the argument to the specified parameter.485 /// </summary>486 /// <param name="parameterSets">487 /// The parameter set used to bind the arguments.488 /// </param>489 /// <param name="argument">490 /// The argument to be bound.491 /// </param>492 /// <param name="parameter">493 /// The metadata for the parameter to bind the argument to.494 /// </param>495 /// <param name="flags">496 /// Flags for type coercion and validation of the arguments.497 /// </param>498 /// <returns>499 /// True if the parameter was successfully bound. False if <paramref name="flags"/>500 /// specifies no type coercion and the type does not match the parameter type.501 /// </returns>502 /// <exception cref="ArgumentNullException">503 /// If <paramref name="parameter"/> or <paramref name="argument"/> is null.504 /// </exception>505 /// <exception cref="ParameterBindingException">506 /// If argument transformation fails.507 /// or508 /// The argument could not be coerced to the appropriate type for the parameter.509 /// or510 /// The parameter argument transformation, prerequisite, or validation failed.511 /// or512 /// If the binding to the parameter fails.513 /// </exception>514 internal virtual bool BindParameter(515 uint parameterSets,516 CommandParameterInternal argument,517 MergedCompiledCommandParameter parameter,518 ParameterBindingFlags flags)519 {520 bool result = false;521 522 switch (parameter.BinderAssociation)523 {524 case ParameterBinderAssociation.DeclaredFormalParameters:525 result =526 this.DefaultParameterBinder.BindParameter(527 argument,528 parameter.Parameter,529 flags);530 break;531 532 default:533 Diagnostics.Assert(534 false,535 "Only the formal parameters are available for this type of command");536 break;537 }538 539 if (result && ((flags & ParameterBindingFlags.IsDefaultValue) == 0))540 {541 UnboundParameters.Remove(parameter);542 BoundParameters.Add(parameter.Parameter.Name, parameter);543 }544 545 return result;546 }547 548 /// <summary>549 /// This is used by <see cref="BindNamedParameters"/> to validate and bind a given named parameter.550 /// </summary>551 protected virtual void BindNamedParameter(552 uint parameterSets,553 CommandParameterInternal argument,554 MergedCompiledCommandParameter parameter)555 {556 BindParameter(parameterSets, argument, parameter, ParameterBindingFlags.ShouldCoerceType);557 }558 559 /// <summary>560 /// Bind the named parameters from the specified argument collection,561 /// for only the parameters in the specified parameter set.562 /// </summary>563 /// <param name="parameterSets">564 /// The parameter set used to bind the arguments.565 /// </param>566 /// <param name="arguments">567 /// The arguments that should be attempted to bind to the parameters of the specified parameter binder.568 /// </param>569 /// <exception cref="ParameterBindingException">570 /// if multiple parameters are found matching the name.571 /// or572 /// if no match could be found.573 /// or574 /// If argument transformation fails.575 /// or576 /// The argument could not be coerced to the appropriate type for the parameter.577 /// or578 /// The parameter argument transformation, prerequisite, or validation failed.579 /// or580 /// If the binding to the parameter fails.581 /// </exception>582 protected Collection<CommandParameterInternal> BindNamedParameters(uint parameterSets, Collection<CommandParameterInternal> arguments)583 {584 Collection<CommandParameterInternal> result = new Collection<CommandParameterInternal>();585 HashSet<string> boundExplicitNamedParams = null;586 587 foreach (CommandParameterInternal argument in arguments)588 {589 if (!argument.ParameterNameSpecified)590 {591 result.Add(argument);592 continue;593 }594 595 // We don't want to throw an exception yet because the parameter might be a positional argument,596 // or in case of a cmdlet or an advanced function, it might match up to a dynamic parameter.597 MergedCompiledCommandParameter parameter =598 BindableParameters.GetMatchingParameter(599 name: argument.ParameterName,600 throwOnParameterNotFound: false,601 tryExactMatching: true,602 invocationInfo: new InvocationInfo(this.InvocationInfo.MyCommand, argument.ParameterExtent));603 604 // If the parameter is not in the specified parameter set, throw a binding exception605 if (parameter != null)606 {607 string formalParamName = parameter.Parameter.Name;608 609 if (argument.FromHashtableSplatting)610 {611 boundExplicitNamedParams ??= new HashSet<string>(612 BoundParameters.Keys,613 StringComparer.OrdinalIgnoreCase);614 615 if (boundExplicitNamedParams.Contains(formalParamName))616 {617 // This named parameter from splatting is also explicitly specified by the user,618 // which was successfully bound, so we ignore the one from splatting because it619 // is superseded by the explicit one. For example:620 // $splat = @{ Path = $path1 }621 // dir @splat -Path $path2622 continue;623 }624 }625 626 // Now check to make sure it hasn't already been627 // bound by looking in the boundParameters collection628 629 if (BoundParameters.ContainsKey(formalParamName))630 {631 ParameterBindingException bindingException =632 new ParameterBindingException(633 ErrorCategory.InvalidArgument,634 this.InvocationInfo,635 GetParameterErrorExtent(argument),636 argument.ParameterName,637 null,638 null,639 ParameterBinderStrings.ParameterAlreadyBound,640 nameof(ParameterBinderStrings.ParameterAlreadyBound));641 642 throw bindingException;643 }644 645 BindNamedParameter(parameterSets, argument, parameter);646 }647 else if (argument.ParameterName.Equals(Parser.VERBATIM_PARAMETERNAME, StringComparison.Ordinal))648 {649 // We sometimes send a magic parameter from a remote machine with the values referenced via650 // a using expression ($using:x). We then access these values via PSBoundParameters, so651 // "bind" them here.652 DefaultParameterBinder.CommandLineParameters.SetImplicitUsingParameters(argument.ArgumentValue);653 }654 else655 {656 result.Add(argument);657 }658 }659 660 return result;661 }662 663 /// <summary>664 /// Binds the unbound arguments to positional parameters.665 /// </summary>666 /// <param name="unboundArguments">667 /// The unbound arguments to attempt to bind as positional arguments.668 /// </param>669 /// <param name="validParameterSets">670 /// The current parameter set flags that are valid.671 /// </param>672 /// <param name="defaultParameterSet">673 /// The parameter set to use to disambiguate parameters that have the same position674 /// </param>675 /// <param name="outgoingBindingException">676 /// Returns the underlying parameter binding exception if any was generated.677 /// </param>678 /// <returns>679 /// The remaining arguments that have not been bound.680 /// </returns>681 /// <remarks>682 /// It is assumed that the unboundArguments parameter has already been processed683 /// for this parameter binder. All named parameters have been paired with their684 /// values. Any arguments that don't have a name are considered positional and685 /// will be processed in this method.686 /// </remarks>687 /// <exception cref="ParameterBindingException">688 /// If multiple parameters were found for the same position in the specified689 /// parameter set.690 /// or691 /// If argument transformation fails.692 /// or693 /// The argument could not be coerced to the appropriate type for the parameter.694 /// or695 /// The parameter argument transformation, prerequisite, or validation failed.696 /// or697 /// If the binding to the parameter fails.698 /// </exception>699 internal Collection<CommandParameterInternal> BindPositionalParameters(700 Collection<CommandParameterInternal> unboundArguments,701 uint validParameterSets,702 uint defaultParameterSet,703 out ParameterBindingException outgoingBindingException704 )705 {706 Collection<CommandParameterInternal> result = new Collection<CommandParameterInternal>();707 outgoingBindingException = null;708 709 if (unboundArguments.Count > 0)710 {711 // Create a new collection to iterate over so that we can remove712 // unbound arguments while binding them.713 714 List<CommandParameterInternal> unboundArgumentsCollection = new List<CommandParameterInternal>(unboundArguments);715 716 // Get a sorted dictionary of the positional parameters with the position717 // as the key718 719 SortedDictionary<int, Dictionary<MergedCompiledCommandParameter, PositionalCommandParameter>> positionalParameterDictionary;720 721 try722 {723 positionalParameterDictionary =724 EvaluateUnboundPositionalParameters(UnboundParameters, _currentParameterSetFlag);725 }726 catch (InvalidOperationException)727 {728 // The parameter set declaration is ambiguous so729 // throw an exception.730 731 ParameterBindingException bindingException =732 new ParameterBindingException(733 ErrorCategory.InvalidArgument,734 this.InvocationInfo,735 null,736 null,737 null,738 null,739 ParameterBinderStrings.AmbiguousPositionalParameterNoName,740 "AmbiguousPositionalParameterNoName");741 742 // This exception is thrown because the binder found two positional parameters743 // from the same parameter set with the same position defined. This is not caused744 // by introducing the default parameter binding.745 throw bindingException;746 }747 748 if (positionalParameterDictionary.Count > 0)749 {750 int unboundArgumentsIndex = 0;751 752 foreach (Dictionary<MergedCompiledCommandParameter, PositionalCommandParameter> nextPositionalParameters in positionalParameterDictionary.Values)753 {754 // Only continue if there are parameters at the specified position. Parameters755 // can be removed as the parameter set gets narrowed down.756 757 if (nextPositionalParameters.Count == 0)758 {759 continue;760 }761 762 CommandParameterInternal argument = GetNextPositionalArgument(763 unboundArgumentsCollection,764 result,765 ref unboundArgumentsIndex);766 767 if (argument == null)768 {769 break;770 }771 772 // Bind first to defaultParameterSet without type coercion, then to773 // other sets without type coercion, then to the defaultParameterSet with774 // type coercion and finally to the other sets with type coercion.775 776 bool aParameterWasBound = false;777 if (defaultParameterSet != 0 && (validParameterSets & defaultParameterSet) != 0)778 {779 // Favor the default parameter set.780 // First try without type coercion781 782 aParameterWasBound =783 BindPositionalParametersInSet(784 defaultParameterSet,785 nextPositionalParameters,786 argument,787 ParameterBindingFlags.DelayBindScriptBlock,788 out outgoingBindingException);789 }790 791 if (!aParameterWasBound)792 {793 // Try the non-default parameter sets794 // without type coercion.795 796 aParameterWasBound =797 BindPositionalParametersInSet(798 validParameterSets,799 nextPositionalParameters,800 argument,801 ParameterBindingFlags.DelayBindScriptBlock,802 out outgoingBindingException);803 }804 805 if (!aParameterWasBound)806 {807 // Now try the default parameter set with type coercion808 if (defaultParameterSet != 0 && (validParameterSets & defaultParameterSet) != 0)809 {810 // Favor the default parameter set.811 // First try without type coercion812 813 aParameterWasBound =814 BindPositionalParametersInSet(815 defaultParameterSet,816 nextPositionalParameters,817 argument,818 ParameterBindingFlags.ShouldCoerceType | ParameterBindingFlags.DelayBindScriptBlock,819 out outgoingBindingException);820 }821 }822 823 if (!aParameterWasBound)824 {825 // Try the non-default parameter sets826 // with type coercion.827 828 aParameterWasBound =829 BindPositionalParametersInSet(830 validParameterSets,831 nextPositionalParameters,832 argument,833 ParameterBindingFlags.ShouldCoerceType | ParameterBindingFlags.DelayBindScriptBlock,834 out outgoingBindingException);835 }836 837 if (!aParameterWasBound)838 {839 // Add the unprocessed argument to the results and continue840 result.Add(argument);841 }842 else843 {844 // Update the parameter sets if necessary845 if (validParameterSets != _currentParameterSetFlag)846 {847 validParameterSets = _currentParameterSetFlag;848 UpdatePositionalDictionary(positionalParameterDictionary, validParameterSets);849 }850 }851 }852 853 // Now for any arguments that were not processed, add them to854 // the result855 856 for (int index = unboundArgumentsIndex; index < unboundArgumentsCollection.Count; ++index)857 {858 result.Add(unboundArgumentsCollection[index]);859 }860 }861 else862 {863 // Since no positional parameters were found, add the arguments864 // to the result865 866 result = unboundArguments;867 }868 }869 870 return result;871 }872 873 /// <summary>874 /// This method only updates the collections contained in the dictionary, not the dictionary875 /// itself to contain only the parameters that are in the specified parameter set.876 /// </summary>877 /// <param name="positionalParameterDictionary">878 /// The sorted dictionary of positional parameters.879 /// </param>880 /// <param name="validParameterSets">881 /// Valid parameter sets882 /// </param>883 internal static void UpdatePositionalDictionary(884 SortedDictionary<int, Dictionary<MergedCompiledCommandParameter, PositionalCommandParameter>> positionalParameterDictionary,885 uint validParameterSets)886 {887 foreach (Dictionary<MergedCompiledCommandParameter, PositionalCommandParameter> parameterCollection in positionalParameterDictionary.Values)888 {889 Collection<MergedCompiledCommandParameter> paramToRemove = new Collection<MergedCompiledCommandParameter>();890 891 foreach (PositionalCommandParameter positionalParameter in parameterCollection.Values)892 {893 Collection<ParameterSetSpecificMetadata> parameterSetData = positionalParameter.ParameterSetData;894 895 for (int index = parameterSetData.Count - 1; index >= 0; --index)896 {897 if ((parameterSetData[index].ParameterSetFlag & validParameterSets) == 0 &&898 !parameterSetData[index].IsInAllSets)899 {900 // The parameter is not in the valid parameter sets so remove it from the collection.901 parameterSetData.RemoveAt(index);902 }903 }904 905 if (parameterSetData.Count == 0)906 {907 paramToRemove.Add(positionalParameter.Parameter);908 }909 }910 911 // Now remove all the parameters that no longer have parameter set data912 foreach (MergedCompiledCommandParameter removeParam in paramToRemove)913 {914 parameterCollection.Remove(removeParam);915 }916 }917 }918 919 private bool BindPositionalParametersInSet(920 uint validParameterSets,921 Dictionary<MergedCompiledCommandParameter, PositionalCommandParameter> nextPositionalParameters,922 CommandParameterInternal argument,923 ParameterBindingFlags flags,924 out ParameterBindingException bindingException925 )926 {927 bool result = false;928 bindingException = null;929 930 foreach (PositionalCommandParameter parameter in nextPositionalParameters.Values)931 {932 foreach (ParameterSetSpecificMetadata parameterSetData in parameter.ParameterSetData)933 {934 // if the parameter is not in the specified parameter set, don't consider it935 936 if ((validParameterSets & parameterSetData.ParameterSetFlag) == 0 &&937 !parameterSetData.IsInAllSets)938 {939 continue;940 }941 942 bool bindResult = false;943 string parameterName = parameter.Parameter.Parameter.Name;944 945 ParameterBindingException parameterBindingExceptionToThrown = null;946 try947 {948 CommandParameterInternal bindableArgument =949 CommandParameterInternal.CreateParameterWithArgument(950 /*parameterAst*/null, parameterName, "-" + parameterName + ":",951 argument.ArgumentAst, argument.ArgumentValue,952 false);953 954 bindResult =955 BindParameter(956 validParameterSets,957 bindableArgument,958 parameter.Parameter,959 flags);960 }961 catch (ParameterBindingArgumentTransformationException pbex)962 {963 parameterBindingExceptionToThrown = pbex;964 }965 catch (ParameterBindingValidationException pbex)966 {967 if (pbex.SwallowException)968 {969 // Just ignore and continue970 bindResult = false;971 bindingException = pbex;972 }973 else974 {975 parameterBindingExceptionToThrown = pbex;976 }977 }978 catch (ParameterBindingParameterDefaultValueException pbex)979 {980 parameterBindingExceptionToThrown = pbex;981 }982 catch (ParameterBindingException e)983 {984 // Just ignore and continue;985 bindResult = false;986 bindingException = e;987 }988 989 if (parameterBindingExceptionToThrown != null)990 {991 if (!DefaultParameterBindingInUse)992 {993 throw parameterBindingExceptionToThrown;994 }995 else996 {997 ThrowElaboratedBindingException(parameterBindingExceptionToThrown);998 }999 }1000 1001 if (bindResult)1002 {1003 result = true;1004 this.CommandLineParameters.MarkAsBoundPositionally(parameterName);1005 break;1006 }1007 }1008 }1009 1010 return result;1011 }1012 1013 /// <summary>1014 /// Generate elaborated binding exception so that the user will know the default binding might cause the failure.1015 /// </summary>1016 /// <param name="pbex"></param>1017 protected void ThrowElaboratedBindingException(ParameterBindingException pbex)1018 {1019 if (pbex == null)1020 {1021 throw PSTraceSource.NewArgumentNullException(nameof(pbex));1022 }1023 1024 Diagnostics.Assert(pbex.ErrorRecord != null, "ErrorRecord should not be null in a ParameterBindingException");1025 1026 // Original error message1027 string oldMsg = pbex.Message;1028 // Default parameters get bound so far1029 StringBuilder defaultParamsGetBound = new StringBuilder();1030 foreach (string paramName in BoundDefaultParameters)1031 {1032 defaultParamsGetBound.Append(CultureInfo.InvariantCulture, $" -{paramName}");1033 }1034 1035 string resourceString = ParameterBinderStrings.DefaultBindingErrorElaborationSingle;1036 if (BoundDefaultParameters.Count > 1)1037 {1038 resourceString = ParameterBinderStrings.DefaultBindingErrorElaborationMultiple;1039 }1040 1041 ParameterBindingException newBindingException =1042 new ParameterBindingException(1043 pbex.InnerException,1044 pbex,1045 resourceString,1046 oldMsg, defaultParamsGetBound);1047 1048 throw newBindingException;1049 }1050 1051 private static CommandParameterInternal GetNextPositionalArgument(1052 List<CommandParameterInternal> unboundArgumentsCollection,1053 Collection<CommandParameterInternal> nonPositionalArguments,1054 ref int unboundArgumentsIndex)1055 {1056 // Find the next positional argument1057 // An argument without a name is considered to be positional since1058 // we are assuming the unboundArguments have been reparsed using1059 // the merged metadata from this parameter binder controller.1060 1061 CommandParameterInternal result = null;1062 while (unboundArgumentsIndex < unboundArgumentsCollection.Count)1063 {1064 CommandParameterInternal argument = unboundArgumentsCollection[unboundArgumentsIndex++];1065 1066 if (!argument.ParameterNameSpecified)1067 {1068 result = argument;1069 break;1070 }1071 1072 nonPositionalArguments.Add(argument);1073 1074 // Now check to see if the next argument needs to be consumed as well.1075 1076 if (unboundArgumentsCollection.Count - 1 >= unboundArgumentsIndex)1077 {1078 argument = unboundArgumentsCollection[unboundArgumentsIndex];1079 1080 if (!argument.ParameterNameSpecified)1081 {1082 // Since the next argument doesn't appear to be a parameter name1083 // consume it as well.1084 1085 nonPositionalArguments.Add(argument);1086 unboundArgumentsIndex++;1087 }1088 }1089 }1090 1091 return result;1092 }1093 1094 /// <summary>1095 /// Gets the unbound positional parameters in a sorted dictionary in the order of their1096 /// positions.1097 /// </summary>1098 /// <returns>1099 /// The sorted dictionary of MergedCompiledCommandParameter metadata with the position1100 /// as the key.1101 /// </returns>1102 internal static SortedDictionary<int, Dictionary<MergedCompiledCommandParameter, PositionalCommandParameter>> EvaluateUnboundPositionalParameters(1103 ICollection<MergedCompiledCommandParameter> unboundParameters, uint validParameterSetFlag)1104 {1105 SortedDictionary<int, Dictionary<MergedCompiledCommandParameter, PositionalCommandParameter>> result =1106 new SortedDictionary<int, Dictionary<MergedCompiledCommandParameter, PositionalCommandParameter>>();1107 1108 if (unboundParameters.Count > 0)1109 {1110 // Loop through the unbound parameters and find a parameter in the specified parameter set1111 // that has a position greater than or equal to the positionalParameterIndex1112 1113 foreach (MergedCompiledCommandParameter parameter in unboundParameters)1114 {1115 bool isInParameterSet = (parameter.Parameter.ParameterSetFlags & validParameterSetFlag) != 0 || parameter.Parameter.IsInAllSets;1116 1117 if (isInParameterSet)1118 {1119 var parameterSetDataCollection = parameter.Parameter.GetMatchingParameterSetData(validParameterSetFlag);1120 foreach (ParameterSetSpecificMetadata parameterSetData in parameterSetDataCollection)1121 {1122 // Skip ValueFromRemainingArguments parameters1123 if (parameterSetData.ValueFromRemainingArguments)1124 {1125 continue;1126 }1127 1128 // Check the position in the parameter set1129 1130 int positionInParameterSet = parameterSetData.Position;1131 1132 if (positionInParameterSet == int.MinValue)1133 {1134 // The parameter is not positional so go to the next one1135 continue;1136 }1137 1138 AddNewPosition(result, positionInParameterSet, parameter, parameterSetData);1139 }1140 }1141 }1142 }1143 1144 return result;1145 }1146 1147 private static void AddNewPosition(1148 SortedDictionary<int, Dictionary<MergedCompiledCommandParameter, PositionalCommandParameter>> result,1149 int positionInParameterSet,1150 MergedCompiledCommandParameter parameter,1151 ParameterSetSpecificMetadata parameterSetData)1152 {1153 Dictionary<MergedCompiledCommandParameter, PositionalCommandParameter> positionalCommandParameters;1154 if (result.TryGetValue(positionInParameterSet, out positionalCommandParameters))1155 {1156 // Check to see if any of the other parameters in this position are in the same parameter set.1157 if (ContainsPositionalParameterInSet(positionalCommandParameters, parameter, parameterSetData.ParameterSetFlag))1158 {1159 // Multiple parameters were found with the same1160 // position. This means the parameter set is ambiguous.1161 1162 // positional parameter could not be resolved1163 1164 // We throw InvalidOperationException, which the1165 // caller will catch and throw a more1166 // appropriate exception.1167 throw PSTraceSource.NewInvalidOperationException();1168 }1169 1170 PositionalCommandParameter positionalCommandParameter;1171 if (!positionalCommandParameters.TryGetValue(parameter, out positionalCommandParameter))1172 {1173 positionalCommandParameter = new PositionalCommandParameter(parameter);1174 positionalCommandParameters.Add(parameter, positionalCommandParameter);1175 }1176 1177 positionalCommandParameter.ParameterSetData.Add(parameterSetData);1178 }1179 else1180 {1181 Dictionary<MergedCompiledCommandParameter, PositionalCommandParameter> newPositionDictionary =1182 new Dictionary<MergedCompiledCommandParameter, PositionalCommandParameter>();1183 1184 PositionalCommandParameter newPositionalParameter = new PositionalCommandParameter(parameter);1185 newPositionalParameter.ParameterSetData.Add(parameterSetData);1186 newPositionDictionary.Add(parameter, newPositionalParameter);1187 1188 result.Add(positionInParameterSet, newPositionDictionary);1189 }1190 }1191 1192 private static bool ContainsPositionalParameterInSet(1193 Dictionary<MergedCompiledCommandParameter, PositionalCommandParameter> positionalCommandParameters,1194 MergedCompiledCommandParameter parameter,1195 uint parameterSet)1196 {1197 bool result = false;1198 1199 foreach (KeyValuePair<MergedCompiledCommandParameter, PositionalCommandParameter> pair in positionalCommandParameters)1200 {