MegaBites-AI/Windows-powershell
0308
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.Generic;6using System.Collections.ObjectModel;7using System.Diagnostics;8using System.Diagnostics.CodeAnalysis;9using System.Globalization;10using System.Linq;11using System.Management.Automation.Internal;12using System.Management.Automation.Language;13using System.Reflection;14 15using Dbg = System.Management.Automation.Diagnostics;16 17namespace System.Management.Automation18{19 /// <summary>20 /// Flags.21 /// </summary>22 [Flags]23 internal enum ParameterBindingFlags24 {25 /// <summary>26 /// No flags specified.27 /// </summary>28 None = 0,29 30 /// <summary>31 /// Set when the argument should be converted to the parameter type.32 /// </summary>33 ShouldCoerceType = 0x01,34 35 /// <summary>36 /// Set when the argument should not be validated or recorded in BoundParameters.37 /// </summary>38 IsDefaultValue = 0x02,39 40 /// <summary>41 /// Set when script blocks can be bound as a script block parameter instead of a normal argument.42 /// </summary>43 DelayBindScriptBlock = 0x04,44 45 /// <summary>46 /// Set when an exception will be thrown if a matching parameter could not be found.47 /// </summary>48 ThrowOnParameterNotFound = 0x08,49 }50 51 /// <summary>52 /// An abstract class used by the CommandProcessor to bind parameters to a bindable object.53 /// Derived classes are used to provide specific binding behavior for different object types,54 /// like Cmdlet, PsuedoParameterCollection, and dynamic parameter objects.55 /// </summary>56 [DebuggerDisplay("Command = {command}")]57 internal abstract class ParameterBinderBase58 {59 #region tracer60 [TraceSource("ParameterBinderBase", "A abstract helper class for the CommandProcessor that binds parameters to the specified object.")]61 private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("ParameterBinderBase", "A abstract helper class for the CommandProcessor that binds parameters to the specified object.");62 63 [TraceSource("ParameterBinding", "Traces the process of binding the arguments to the parameters of cmdlets, scripts, and applications.")]64 internal static readonly PSTraceSource bindingTracer =65 PSTraceSource.GetTracer(66 "ParameterBinding",67 "Traces the process of binding the arguments to the parameters of cmdlets, scripts, and applications.",68 false);69 70 #endregion tracer71 72 #region ctor73 74 /// <summary>75 /// Constructs the parameter binder with the specified type metadata. The binder is only valid76 /// for a single instance of a bindable object and only for the duration of a command.77 /// </summary>78 /// <param name="target">79 /// The target object that the parameter values will be bound to.80 /// </param>81 /// <param name="invocationInfo">82 /// The invocation information for the code that is being bound.83 /// </param>84 /// <param name="context">85 /// The context of the currently running engine.86 /// </param>87 /// <param name="command">88 /// The command that the parameter binder is binding to. The command can be null.89 /// </param>90 internal ParameterBinderBase(91 object target,92 InvocationInfo invocationInfo,93 ExecutionContext context,94 InternalCommand command)95 {96 Diagnostics.Assert(target != null, "caller to verify target is not null.");97 Diagnostics.Assert(invocationInfo != null, "caller to verify invocationInfo is not null.");98 Diagnostics.Assert(context != null, "caller to verify context is not null.");99 100 bindingTracer.ShowHeaders = false;101 102 _command = command;103 _target = target;104 _invocationInfo = invocationInfo;105 _context = context;106 _engine = context.EngineIntrinsics;107 _isTranscribing = context.EngineHostInterface.UI.IsTranscribing;108 }109 110 /// <summary>111 /// Constructs the parameter binder with the specified type metadata. The binder is only valid112 /// for a single instance of a bindable object and only for the duration of a command.113 /// </summary>114 /// <param name="invocationInfo">115 /// The invocation information for the code that is being bound.116 /// </param>117 /// <param name="context">118 /// The context of the currently running engine.119 /// </param>120 /// <param name="command">121 /// The command that the parameter binder is binding to. The command can be null.122 /// </param>123 internal ParameterBinderBase(124 InvocationInfo invocationInfo,125 ExecutionContext context,126 InternalCommand command)127 {128 Diagnostics.Assert(invocationInfo != null, "caller to verify invocationInfo is not null.");129 Diagnostics.Assert(context != null, "caller to verify context is not null.");130 131 bindingTracer.ShowHeaders = false;132 133 _command = command;134 _invocationInfo = invocationInfo;135 _context = context;136 _engine = context.EngineIntrinsics;137 _isTranscribing = context.EngineHostInterface.UI.IsTranscribing;138 }139 140 #endregion ctor141 142 #region internal members143 144 /// <summary>145 /// Gets or sets the bindable object that the binder will bind parameters to.146 /// </summary>147 /// <value></value>148 internal object Target149 {150 get151 {152 Diagnostics.Assert(153 _target != null,154 "The target should always be set for the binder");155 156 return _target;157 }158 159 set160 {161 _target = value;162 }163 }164 165 /// <summary>166 /// The bindable object that parameters will be bound to.167 /// </summary>168 private object _target;169 170 /// <summary>171 /// Holds the set of parameters that have been bound from the command line...172 /// </summary>173 internal CommandLineParameters CommandLineParameters174 {175 get { return _commandLineParameters ??= new CommandLineParameters(); }176 177 // Setter is needed to pass into RuntimeParameterBinder instances178 set { _commandLineParameters = value; }179 }180 181 private CommandLineParameters _commandLineParameters;182 183 /// <summary>184 /// If this is true, then we want to record the list of bound parameters...185 /// </summary>186 internal bool RecordBoundParameters = true;187 188 /// <summary>189 /// Full Qualified ID for the obsolete parameter warning.190 /// </summary>191 internal const string FQIDParameterObsolete = "ParameterObsolete";192 193 #region Parameter default values194 195 /// <summary>196 /// Derived classes must override this method to get the default parameter197 /// value so that it can be restored between pipeline input.198 /// </summary>199 /// <param name="name">200 /// The name of the parameter to get the default value of.201 /// </param>202 /// <returns>203 /// The value of the parameter specified by name.204 /// </returns>205 internal abstract object GetDefaultParameterValue(string name);206 207 #endregion Parameter default values208 209 #region Parameter binding210 211 /// <summary>212 /// Derived classes define this method to bind the specified value213 /// to the specified parameter.214 /// </summary>215 /// <param name="name">216 /// The name of the parameter to bind the value to.217 /// </param>218 /// <param name="value">219 /// The value to bind to the parameter. It should be assumed by220 /// derived classes that the proper type coercion has already taken221 /// place and that any validation metadata has been satisfied.222 /// </param>223 /// <param name="parameterMetadata"></param>224 internal abstract void BindParameter(string name, object value, CompiledCommandParameter parameterMetadata);225 226 private void ValidatePSTypeName(227 CommandParameterInternal parameter,228 CompiledCommandParameter parameterMetadata,229 bool retryOtherBindingAfterFailure,230 object parameterValue)231 {232 Dbg.Assert(parameter != null, "Caller should verify parameter != null");233 Dbg.Assert(parameterMetadata != null, "Caller should verify parameterMetadata != null");234 235 if (parameterValue == null)236 {237 return;238 }239 240 IEnumerable<string> psTypeNamesOfArgumentValue = PSObject.AsPSObject(parameterValue).InternalTypeNames;241 string psTypeNameRequestedByParameter = parameterMetadata.PSTypeName;242 243 if (!psTypeNamesOfArgumentValue.Contains(psTypeNameRequestedByParameter, StringComparer.OrdinalIgnoreCase))244 {245 // win8: 228176..The callers know when to ignore and when not to ignore invalid cast exceptions.246 PSInvalidCastException e = new PSInvalidCastException(nameof(ErrorCategory.InvalidArgument),247 null,248 ParameterBinderStrings.MismatchedPSTypeName,249 (_invocationInfo != null) && (_invocationInfo.MyCommand != null) ? _invocationInfo.MyCommand.Name : string.Empty,250 parameterMetadata.Name,251 parameterMetadata.Type,252 parameterValue.GetType(),253 0,254 0,255 psTypeNameRequestedByParameter);256 257 ParameterBindingException parameterBindingException;258 if (!retryOtherBindingAfterFailure)259 {260 parameterBindingException = new ParameterBindingArgumentTransformationException(261 e,262 ErrorCategory.InvalidArgument,263 this.InvocationInfo,264 GetErrorExtent(parameter),265 parameterMetadata.Name,266 parameterMetadata.Type,267 parameterValue.GetType(),268 ParameterBinderStrings.MismatchedPSTypeName,269 "MismatchedPSTypeName",270 psTypeNameRequestedByParameter);271 }272 else273 {274 parameterBindingException = new ParameterBindingException(275 e,276 ErrorCategory.InvalidArgument,277 this.InvocationInfo,278 GetErrorExtent(parameter),279 parameterMetadata.Name,280 parameterMetadata.Type,281 parameterValue.GetType(),282 ParameterBinderStrings.MismatchedPSTypeName,283 "MismatchedPSTypeName",284 psTypeNameRequestedByParameter);285 }286 287 throw parameterBindingException;288 }289 }290 291 /// <summary>292 /// Does all the type coercion, data generation, and validation necessary to bind the293 /// parameter, then calls the protected BindParameter method to have294 /// the derived class do the actual binding.295 /// </summary>296 /// <param name="parameter">297 /// The parameter to be bound.298 /// </param>299 /// <param name="parameterMetadata">300 /// The metadata for the parameter to use in guiding the binding.301 /// </param>302 /// <param name="flags">303 /// Flags for type coercion and validation.304 /// </param>305 /// <returns>306 /// True if the parameter was successfully bound. False if <paramref name="coerceTypeIfNeeded"/>307 /// is false and the type does not match the parameter type.308 /// </returns>309 /// <remarks>310 /// The binding algorithm goes as follows:311 /// 1. The data generation attributes are run312 /// 2. The data is coerced into the correct type313 /// 3. The data if validated using the validation attributes314 /// 4. The data is encoded into the bindable object using the315 /// protected BindParameter method.316 /// </remarks>317 /// <exception cref="ArgumentNullException">318 /// If <paramref name="parameter"/> or <paramref name="parameterMetadata"/> is null.319 /// </exception>320 /// <exception cref="ParameterBindingException">321 /// If argument transformation fails.322 /// or323 /// The argument could not be coerced to the appropriate type for the parameter.324 /// or325 /// The parameter argument transformation, prerequisite, or validation failed.326 /// or327 /// If the binding to the parameter fails.328 /// </exception>329 internal virtual bool BindParameter(330 CommandParameterInternal parameter,331 CompiledCommandParameter parameterMetadata,332 ParameterBindingFlags flags)333 {334 bool result = false;335 bool coerceTypeIfNeeded = (flags & ParameterBindingFlags.ShouldCoerceType) != 0;336 bool isDefaultValue = (flags & ParameterBindingFlags.IsDefaultValue) != 0;337 338 if (parameter == null)339 {340 throw PSTraceSource.NewArgumentNullException(nameof(parameter));341 }342 343 if (parameterMetadata == null)344 {345 throw PSTraceSource.NewArgumentNullException(nameof(parameterMetadata));346 }347 348 using (bindingTracer.TraceScope(349 "BIND arg [{0}] to parameter [{1}]",350 parameter.ArgumentValue,351 parameterMetadata.Name))352 {353 // Set the complete parameter name354 355 parameter.ParameterName = parameterMetadata.Name;356 357 object parameterValue = parameter.ArgumentValue;358 359 do // false loop360 {361 // Now call any argument transformation attributes that might be present on the parameter362 ScriptParameterBinder spb = this as ScriptParameterBinder;363 bool usesCmdletBinding = false;364 if (spb != null)365 {366 usesCmdletBinding = spb.Script.UsesCmdletBinding;367 }368 369 // Now do the argument transformation. No transformation is done for the default values in script that meet the following 2 conditions:370 // 1. the default value is not specified by the user, but is the powershell default value.371 // e.g. the powershell default value for a class type is null, for the string type is string.empty.372 // 2. the powershell default value is null.373 // This is to prevent ArgumentTransformationAttributes from making a non-mandatory parameter behave like a mandatory one.374 // Example:375 // ## without the fix, 'CredentialAttribute' would make $Credential like a mandatory parameter376 // ## 'PS> test-credential' would prompt for credential input377 // function test-credential {378 // param(379 // [System.Management.Automation.CredentialAttribute()]380 // $Credential381 // )382 // $Credential383 // }384 //385 foreach (ArgumentTransformationAttribute dma in parameterMetadata.ArgumentTransformationAttributes)386 {387 using (bindingTracer.TraceScope(388 "Executing DATA GENERATION metadata: [{0}]",389 dma.GetType()))390 {391 try392 {393 ArgumentTypeConverterAttribute argumentTypeConverter = dma as ArgumentTypeConverterAttribute;394 395 if (argumentTypeConverter != null)396 {397 if (coerceTypeIfNeeded)398 {399 parameterValue = argumentTypeConverter.Transform(_engine, parameterValue, true, usesCmdletBinding);400 }401 }402 else403 {404 // Only apply argument transformation when the argument is not null, is mandatory, or disallows null as a value.405 //406 // If we are binding default value for an unbound script parameter, this parameter is guaranteed not mandatory407 // in the chosen parameter set. This is because:408 // 1. If we use cmdlet binding for this script (CmdletParameterBinderController is used), then binding409 // default value to unbound parameters won't happen until after all mandatory parameters from the410 // chosen parameter set are handled. Therefore, the unbound parameter we are dealing with here won't411 // be mandatory in the chosen parameter set.412 // 2. If we use script binding (ScriptParameterBinderController is used), then parameters won't have the413 // ParameterAttribute declared for them, and thus are definitely not mandatory.414 // So we check 'IsParameterMandatory' only if we are not binding default values.415 if ((parameterValue != null) ||416 (!isDefaultValue && (parameterMetadata.IsMandatoryInSomeParameterSet ||417 parameterMetadata.CannotBeNull ||418 dma.TransformNullOptionalParameters)))419 {420 parameterValue = dma.TransformInternal(_engine, parameterValue);421 }422 }423 424 bindingTracer.WriteLine(425 "result returned from DATA GENERATION: {0}",426 parameterValue);427 }428 catch (Exception e) // Catch-all OK, 3rd party callout429 {430 bindingTracer.WriteLine(431 "ERROR: DATA GENERATION: {0}",432 e.Message);433 434 ParameterBindingException bindingException =435 new ParameterBindingArgumentTransformationException(436 e,437 ErrorCategory.InvalidData,438 this.InvocationInfo,439 GetErrorExtent(parameter),440 parameterMetadata.Name,441 parameterMetadata.Type,442 parameterValue?.GetType(),443 ParameterBinderStrings.ParameterArgumentTransformationError,444 "ParameterArgumentTransformationError",445 e.Message);446 throw bindingException;447 }448 }449 }450 451 // Only try to coerce the type if asked. If not asked,452 // see if the value type matches or is a subclass of453 // the parameter type.454 455 if (coerceTypeIfNeeded)456 {457 // Now do the type coercion458 459 parameterValue =460 CoerceTypeAsNeeded(461 parameter,462 parameterMetadata.Name,463 parameterMetadata.Type,464 parameterMetadata.CollectionTypeInformation,465 parameterValue);466 }467 else468 {469 if (!ShouldContinueUncoercedBind(parameter, parameterMetadata, flags, ref parameterValue))470 {471 // Don't attempt the bind because the value472 // is not of the correct473 // type for the parameter.474 break;475 }476 }477 478 if ((parameterMetadata.PSTypeName != null) && (parameterValue != null))479 {480 IEnumerable parameterValueAsEnumerable = LanguagePrimitives.GetEnumerable(parameterValue);481 if (parameterValueAsEnumerable != null)482 {483 foreach (object o in parameterValueAsEnumerable)484 {485 this.ValidatePSTypeName(parameter, parameterMetadata, !coerceTypeIfNeeded, o);486 }487 }488 else489 {490 this.ValidatePSTypeName(parameter, parameterMetadata, !coerceTypeIfNeeded, parameterValue);491 }492 }493 494 // Now do the data validation. No validation is done for default values in script as that is495 // one way for people to have a known "bad" value to detect unspecified parameters.496 497 if (!isDefaultValue)498 {499 for (int i = 0; i < parameterMetadata.ValidationAttributes.Length; i++)500 {501 var validationAttribute = parameterMetadata.ValidationAttributes[i];502 503 using (bindingTracer.TraceScope(504 "Executing VALIDATION metadata: [{0}]",505 validationAttribute.GetType()))506 {507 try508 {509 validationAttribute.InternalValidate(parameterValue, _engine);510 }511 catch (Exception e) // Catch-all OK, 3rd party callout512 {513 bindingTracer.WriteLine(514 "ERROR: VALIDATION FAILED: {0}",515 e.Message);516 517 ParameterBindingValidationException bindingException =518 new ParameterBindingValidationException(519 e,520 ErrorCategory.InvalidData,521 this.InvocationInfo,522 GetErrorExtent(parameter),523 parameterMetadata.Name,524 parameterMetadata.Type,525 parameterValue?.GetType(),526 ParameterBinderStrings.ParameterArgumentValidationError,527 "ParameterArgumentValidationError",528 e.Message);529 throw bindingException;530 }531 532 s_tracer.WriteLine("Validation attribute on {0} returned {1}.", parameterMetadata.Name, result);533 }534 }535 536 // If the is null, an empty string, or an empty collection,537 // check the parameter metadata to ensure that binding can continue538 // This method throws an appropriate ParameterBindingException539 // if binding cannot continue. If it returns then binding can540 // proceed.541 if (parameterMetadata.IsMandatoryInSomeParameterSet)542 {543 ValidateNullOrEmptyArgument(parameter, parameterMetadata, parameterMetadata.Type, parameterValue, true);544 }545 }546 547 // Write out obsolete parameter warning only if548 // 1. We are binding parameters for a simple function/script549 // 2. We are not binding a default parameter value550 551 if (parameterMetadata.ObsoleteAttribute != null &&552 (!isDefaultValue) &&553 spb != null && !usesCmdletBinding)554 {555 string obsoleteWarning = string.Format(556 CultureInfo.InvariantCulture,557 ParameterBinderStrings.UseOfDeprecatedParameterWarning,558 parameterMetadata.Name,559 parameterMetadata.ObsoleteAttribute.Message);560 561 var mshCommandRuntime = this.Command.commandRuntime as MshCommandRuntime;562 563 // Write out warning only if we are in the context of MshCommandRuntime.564 // This is because565 // 1. The overload method WriteWarning(WarningRecord) is only available in MshCommandRuntime;566 // 2. We write out warnings for obsolete commands and obsolete cmdlet parameters only when in567 // the context of MshCommandRuntime. So we do it here to keep consistency.568 mshCommandRuntime?.WriteWarning(new WarningRecord(FQIDParameterObsolete, obsoleteWarning));569 }570 571 // Finally bind the argument to the parameter572 573 Exception bindError = null;574 575 try576 {577 BindParameter(parameter.ParameterName, parameterValue, parameterMetadata);578 result = true;579 }580 catch (SetValueException setValueException)581 {582 bindError = setValueException;583 }584 585 if (bindError != null)586 {587 Type specifiedType = parameterValue?.GetType();588 ParameterBindingException bindingException =589 new ParameterBindingException(590 bindError,591 ErrorCategory.WriteError,592 this.InvocationInfo,593 GetErrorExtent(parameter),594 parameterMetadata.Name,595 parameterMetadata.Type,596 specifiedType,597 ParameterBinderStrings.ParameterBindingFailed,598 "ParameterBindingFailed",599 bindError.Message);600 601 throw bindingException;602 }603 }604 while (false);605 606 bindingTracer.WriteLine(607 "BIND arg [{0}] to param [{1}] {2}",608 parameterValue,609 parameter.ParameterName,610 (result) ? "SUCCESSFUL" : "SKIPPED");611 612 if (result)613 {614 // Add this name to the set of bound parameters...615 if (RecordBoundParameters)616 {617 this.CommandLineParameters.Add(parameter.ParameterName, parameterValue);618 }619 620 MshCommandRuntime cmdRuntime = this.Command.commandRuntime as MshCommandRuntime;621 if ((cmdRuntime != null) &&622 (cmdRuntime.LogPipelineExecutionDetail || _isTranscribing) &&623 (cmdRuntime.PipelineProcessor != null))624 {625 string stringToPrint = null;626 try627 {628 // Unroll parameter value629 IEnumerable values = LanguagePrimitives.GetEnumerable(parameterValue);630 if (values != null)631 {632 var sb = new Text.StringBuilder(256);633 var sep = string.Empty;634 foreach (var value in values)635 {636 sb.Append(sep);637 sep = ", ";638 sb.Append(value);639 // For better performance, avoid logging too much640 if (sb.Length > 256)641 {642 sb.Append(", ...");643 break;644 }645 }646 647 stringToPrint = sb.ToString();648 }649 else if (parameterValue != null)650 {651 stringToPrint = parameterValue.ToString();652 }653 }654 catch (Exception) // Catch-all OK, 3rd party callout655 {656 }657 658 if (stringToPrint != null)659 {660 cmdRuntime.PipelineProcessor.LogExecutionParameterBinding(this.InvocationInfo, parameter.ParameterName, stringToPrint);661 }662 }663 }664 665 return result;666 }667 }668 669 /// <summary>670 /// This method ensures that if the parameter is mandatory, and AllowNull, AllowEmptyString,671 /// and/or AllowEmptyCollection is not specified, then argument is not null or empty.672 /// </summary>673 /// <param name="parameter">674 /// The argument token.675 /// </param>676 /// <param name="parameterMetadata">677 /// The metadata for the parameter.678 /// </param>679 /// <param name="argumentType">680 /// The type of the argument to validate against.681 /// </param>682 /// <param name="parameterValue">683 /// The value that will be bound to the parameter.684 /// </param>685 /// <param name="recurseIntoCollections">686 /// If true, then elements of collections will be validated against the metadata.687 /// </param>688 private void ValidateNullOrEmptyArgument(689 CommandParameterInternal parameter,690 CompiledCommandParameter parameterMetadata,691 Type argumentType,692 object parameterValue,693 bool recurseIntoCollections)694 {695 if (parameterValue == null && argumentType != typeof(bool?))696 {697 if (!parameterMetadata.AllowsNullArgument)698 {699 bindingTracer.WriteLine("ERROR: Argument cannot be null");700 701 ParameterBindingValidationException bindingException =702 new ParameterBindingValidationException(703 ErrorCategory.InvalidData,704 this.InvocationInfo,705 GetErrorExtent(parameter),706 parameterMetadata.Name,707 argumentType,708 null,709 ParameterBinderStrings.ParameterArgumentValidationErrorNullNotAllowed,710 "ParameterArgumentValidationErrorNullNotAllowed");711 throw bindingException;712 }713 714 return;715 }716 717 if (argumentType == typeof(string))718 {719 // Since the parameter is of type string, verify that either the argument720 // is not null and not empty or that the parameter can accept null or empty.721 string stringParamValue = parameterValue as string;722 Diagnostics.Assert(723 stringParamValue != null,724 "Type coercion should have already converted the argument value to a string");725 726 if (stringParamValue.Length == 0 && !parameterMetadata.AllowsEmptyStringArgument)727 {728 bindingTracer.WriteLine("ERROR: Argument cannot be an empty string");729 730 ParameterBindingValidationException bindingException =731 new ParameterBindingValidationException(732 ErrorCategory.InvalidData,733 this.InvocationInfo,734 GetErrorExtent(parameter),735 parameterMetadata.Name,736 parameterMetadata.Type,737 parameterValue?.GetType(),738 ParameterBinderStrings.ParameterArgumentValidationErrorEmptyStringNotAllowed,739 "ParameterArgumentValidationErrorEmptyStringNotAllowed");740 throw bindingException;741 }742 743 return;744 }745 746 if (!recurseIntoCollections)747 return;748 749 switch (parameterMetadata.CollectionTypeInformation.ParameterCollectionType)750 {751 case ParameterCollectionType.IList:752 case ParameterCollectionType.Array:753 case ParameterCollectionType.ICollectionGeneric:754 break;755 default:756 // not a recognized collection, no need to recurse757 return;758 }759 760 // All these collection types implement IEnumerable761 IEnumerator ienum = LanguagePrimitives.GetEnumerator(parameterValue);762 Diagnostics.Assert(763 ienum != null,764 "Type coercion should have already converted the argument value to an IEnumerator");765 766 // Ensure that each element abides by the metadata767 bool isEmpty = true;768 Type elementType = parameterMetadata.CollectionTypeInformation.ElementType;769 bool isElementValueType = elementType != null && elementType.IsValueType;770 771 // Note - we explicitly don't pass the context here because we don't want772 // the overhead of the calls that check for stopping.773 if (ParserOps.MoveNext(null, null, ienum))774 {775 isEmpty = false;776 }777 778 // If the element of the collection is of value type, then no need to check for null779 // because a value-type value cannot be null.780 if (!isEmpty && !isElementValueType)781 {782 do783 {784 object element = ParserOps.Current(null, ienum);785 ValidateNullOrEmptyArgument(786 parameter,787 parameterMetadata,788 parameterMetadata.CollectionTypeInformation.ElementType,789 element,790 false);791 } while (ParserOps.MoveNext(null, null, ienum));792 }793 794 if (isEmpty && !parameterMetadata.AllowsEmptyCollectionArgument)795 {796 bindingTracer.WriteLine("ERROR: Argument cannot be an empty collection");797 798 string errorId, resourceString;799 if (parameterMetadata.CollectionTypeInformation.ParameterCollectionType == ParameterCollectionType.Array)800 {801 errorId = "ParameterArgumentValidationErrorEmptyArrayNotAllowed";802 resourceString = ParameterBinderStrings.ParameterArgumentValidationErrorEmptyArrayNotAllowed;803 }804 else805 {806 errorId = "ParameterArgumentValidationErrorEmptyCollectionNotAllowed";807 resourceString = ParameterBinderStrings.ParameterArgumentValidationErrorEmptyCollectionNotAllowed;808 }809 810 ParameterBindingValidationException bindingException =811 new ParameterBindingValidationException(812 ErrorCategory.InvalidData,813 this.InvocationInfo,814 GetErrorExtent(parameter),815 parameterMetadata.Name,816 parameterMetadata.Type,817 parameterValue?.GetType(),818 resourceString,819 errorId);820 throw bindingException;821 }822 }823 824 private bool ShouldContinueUncoercedBind(825 CommandParameterInternal parameter,826 CompiledCommandParameter parameterMetadata,827 ParameterBindingFlags flags,828 ref object parameterValue)829 {830 bool isDefaultValue = (flags & ParameterBindingFlags.IsDefaultValue) != 0;831 Type parameterType = parameterMetadata.Type;832 833 if (parameterValue == null)834 {835 return parameterType == null ||836 isDefaultValue ||837 (!parameterType.IsValueType &&838 parameterType != typeof(string));839 }840 841 // If the types are not a direct match, or842 // the value type is not a subclass of the parameter type, or843 // the value is an PSObject and the parameter type is not object and844 // the PSObject.BaseObject type does not match or is not a subclass845 // of the parameter type, or846 // the value must be encoded into a collection but it is not of the correct element type847 //848 // then return false849 850 if (parameterType.IsInstanceOfType(parameterValue))851 {852 return true;853 }854 855 var psobj = parameterValue as PSObject;856 if (psobj != null && !psobj.ImmediateBaseObjectIsEmpty)857 {858 // See if the base object is of the same type or859 // as subclass of the parameter860 861 parameterValue = psobj.BaseObject;862 863 if (parameterType.IsInstanceOfType(parameterValue))864 {865 return true;866 }867 }868 869 // Maybe the parameter type is a collection and the value needs to870 // be encoded871 872 if (parameterMetadata.CollectionTypeInformation.ParameterCollectionType != ParameterCollectionType.NotCollection)873 {874 // See if the value needs to be encoded in a collection875 876 bool coercionRequired;877 object encodedValue =878 EncodeCollection(879 parameter,880 parameterMetadata.Name,881 parameterMetadata.CollectionTypeInformation,882 parameterType,883 parameterValue,884 false,885 out coercionRequired);886 887 if (encodedValue == null || coercionRequired)888 {889 // Don't attempt the bind because the890 // PSObject BaseObject is not of the correct891 // type for the parameter.892 return false;893 }894 895 parameterValue = encodedValue;896 return true;897 }898 899 return false;900 }901 902 #endregion Parameter binding903 904 /// <summary>905 /// The invocation information for the code that is being bound.906 /// </summary>907 private readonly InvocationInfo _invocationInfo;908 909 internal InvocationInfo InvocationInfo910 {911 get912 {913 return _invocationInfo;914 }915 }916 917 /// <summary>918 /// The context of the currently running engine.919 /// </summary>920 private readonly ExecutionContext _context;921 922 internal ExecutionContext Context923 {924 get925 {926 return _context;927 }928 }929 930 /// <summary>931 /// An instance of InternalCommand that the binder is binding to.932 /// </summary>933 private readonly InternalCommand _command;934 935 internal InternalCommand Command936 {937 get938 {939 return _command;940 }941 }942 943 /// <summary>944 /// The engine APIs that need to be passed the attributes when evaluated.945 /// </summary>946 private readonly EngineIntrinsics _engine;947 948 private readonly bool _isTranscribing;949 950 #endregion internal members951 952 #region Private helpers953 954 /// <summary>955 /// Coerces the argument type to the parameter value type as needed.956 /// </summary>957 /// <param name="argument">958 /// The argument as was specified by the command line.959 /// </param>960 /// <param name="parameterName">961 /// The name of the parameter that the coercion is taking place to bind to. It is962 /// used only for error reporting.963 /// </param>964 /// <param name="toType">965 /// The type to coerce the value to.966 /// </param>967 /// <param name="collectionTypeInfo">968 /// The information about the collection type, like element type, etc.969 /// </param>970 /// <param name="currentValue">971 /// The current value of the argument.972 /// </param>973 /// <returns>974 /// The value of the argument in the type of the parameter.975 /// </returns>976 /// <exception cref="ArgumentNullException">977 /// If <paramref name="argument"/> or <paramref name="toType"/> is null.978 /// </exception>979 /// <exception cref="ParameterBindingException">980 /// If the argument value is missing and the parameter is not a bool or SwitchParameter.981 /// or982 /// If the argument value could not be converted to the parameter type.983 /// </exception>984 private object CoerceTypeAsNeeded(985 CommandParameterInternal argument,986 string parameterName,987 Type toType,988 ParameterCollectionTypeInformation collectionTypeInfo,989 object currentValue)990 {991 if (argument == null)992 {993 throw PSTraceSource.NewArgumentNullException(nameof(argument));994 }995 996 if (toType == null)997 {998 throw PSTraceSource.NewArgumentNullException(nameof(toType));999 }1000 1001 // Construct the collection type information if it wasn't passed in.1002 1003 collectionTypeInfo ??= new ParameterCollectionTypeInformation(toType);1004 1005 object originalValue = currentValue;1006 object result = currentValue;1007 1008 using (bindingTracer.TraceScope(1009 "COERCE arg to [{0}]", toType))1010 {1011 Type argumentType = null;1012 try1013 {1014 do // false loop1015 {1016 if (IsNullParameterValue(currentValue))1017 {1018 result = HandleNullParameterForSpecialTypes(argument, parameterName, toType, currentValue);1019 break;1020 }1021 1022 // Do the coercion1023 argumentType = currentValue.GetType();1024 1025 // If the types are identical (or can be cast directly,) then no coercion1026 // needs to be done1027 if (toType.IsAssignableFrom(argumentType))1028 {1029 bindingTracer.WriteLine(1030 "Parameter and arg types the same, no coercion is needed.");1031 1032 result = currentValue;1033 break;1034 }1035 1036 bindingTracer.WriteLine("Trying to convert argument value from {0} to {1}", argumentType, toType);1037 1038 // Likewise shortcircuit the case were the user has asked for a shell object.1039 // He always gets a shell object regardless of the actual type of the object.1040 if (toType == typeof(PSObject))1041 {1042 // It may be the case that we're binding the current pipeline object1043 // as is to a PSObject parameter in which case, we want to make1044 // sure that we're using the same shell object instead of creating an1045 // alias object.1046 if (_command != null &&1047 currentValue == _command.CurrentPipelineObject.BaseObject)1048 {1049 currentValue = _command.CurrentPipelineObject;1050 }1051 1052 bindingTracer.WriteLine(1053 "The parameter is of type [{0}] and the argument is an PSObject, so the parameter value is the argument value wrapped into an PSObject.",1054 toType);1055 result = LanguagePrimitives.AsPSObjectOrNull(currentValue);1056 break;1057 }1058 1059 // NTRAID#Windows OS Bugs-1064175-2004/02/28-JeffJon1060 // If we have an PSObject with null base and we are trying to1061 // convert to a string, then we need to use null instead of1062 // calling LanguagePrimitives.ConvertTo as that will return1063 // string.Empty.1064 1065 if (toType == typeof(string) &&1066 argumentType == typeof(PSObject))1067 {1068 PSObject currentValueAsPSObject = (PSObject)currentValue;1069 1070 if (currentValueAsPSObject == AutomationNull.Value)1071 {1072 bindingTracer.WriteLine(1073 "CONVERT a null PSObject to a null string.");1074 result = null;1075 break;1076 }1077 }1078 1079 // NTRAID#Windows OS Bugs -<bug id here> - Nana1080 // If we have a boolean, we have to ensure that it can1081 // only take parameters of type boolean or numbers with1082 // 0 indicating false and everything else indicating true1083 // Anything else passed should be reported as an error1084 1085 if (toType == typeof(bool) || toType == typeof(SwitchParameter) ||1086 toType == typeof(bool?))1087 {1088 Type boType = null;1089 if (argumentType == typeof(PSObject))1090 {1091 // Unwrap the PSObject at this point...1092 PSObject currentValueAsPSObject = (PSObject)currentValue;1093 currentValue = currentValueAsPSObject.BaseObject;1094 1095 if (currentValue is SwitchParameter)1096 {1097 currentValue = ((SwitchParameter)currentValue).IsPresent;1098 }1099 1100 boType = currentValue.GetType();1101 }1102 else1103 {1104 boType = argumentType;1105 }1106 1107 if (boType == typeof(bool))1108 {1109 if (LanguagePrimitives.IsBooleanType(toType))1110 result = ParserOps.BoolToObject((bool)currentValue);1111 else1112 result = new SwitchParameter((bool)currentValue);1113 }1114 else if (boType == typeof(int))1115 {1116 if ((int)LanguagePrimitives.ConvertTo(currentValue,1117 typeof(int), CultureInfo.InvariantCulture) != 0)1118 {1119 if (LanguagePrimitives.IsBooleanType(toType))1120 result = ParserOps.BoolToObject(true);1121 else1122 result = new SwitchParameter(true);1123 }1124 else1125 {1126 if (LanguagePrimitives.IsBooleanType(toType))1127 result = ParserOps.BoolToObject(false);1128 else1129 result = new SwitchParameter(false);1130 }1131 }1132 else if (LanguagePrimitives.IsNumeric(boType.GetTypeCode()))1133 {1134 double currentValueAsDouble = (double)LanguagePrimitives.ConvertTo(1135 currentValue, typeof(double), CultureInfo.InvariantCulture);1136 1137 if (currentValueAsDouble != 0)1138 {1139 if (LanguagePrimitives.IsBooleanType(toType))1140 result = ParserOps.BoolToObject(true);1141 else1142 result = new SwitchParameter(true);1143 }1144 else1145 {1146 if (LanguagePrimitives.IsBooleanType(toType))1147 result = ParserOps.BoolToObject(false);1148 else1149 result = new SwitchParameter(false);1150 }1151 }1152 else1153 {1154 // Invalid types which cannot be associated with a bool1155 // Since there is a catch block which appropriately1156 // handles this situation we just throw an exception here1157 // throw new PSInvalidCastException();1158 ParameterBindingException pbe =1159 new ParameterBindingException(1160 ErrorCategory.InvalidArgument,1161 this.InvocationInfo,1162 GetErrorExtent(argument),1163 parameterName,1164 toType,1165 argumentType,1166 ParameterBinderStrings.CannotConvertArgument,1167 "CannotConvertArgument",1168 boType,1169 string.Empty);1170 1171 throw pbe;1172 }1173 1174 break;1175 }1176 1177 // NTRAID#Windows OS Bugs-1009284-2004/05/05-JeffJon1178 // Need to handle other collection types here as well1179 1180 // Before attempting to encode a collection, we check if we can convert the argument directly via1181 // a restricted set of conversions (as the general conversion mechanism is far too general, it may1182 // succeed where it shouldn't. We don't bother checking arrays parameters because they won't have1183 // conversions we're allowing.1184 if (collectionTypeInfo.ParameterCollectionType == ParameterCollectionType.ICollectionGeneric1185 || collectionTypeInfo.ParameterCollectionType == ParameterCollectionType.IList)1186 {1187 object currentValueToConvert = PSObject.Base(currentValue);1188 if (currentValueToConvert != null)1189 {1190 ConversionRank rank = LanguagePrimitives.GetConversionRank(currentValueToConvert.GetType(), toType);1191 if (rank == ConversionRank.Constructor || rank == ConversionRank.ImplicitCast || rank == ConversionRank.ExplicitCast)1192 {1193 // This conversion will fail in the common case, and when it does, we'll use EncodeCollection below.1194 if (LanguagePrimitives.TryConvertTo(currentValue, toType, CultureInfo.CurrentCulture, out result))1195 {1196 break;1197 }1198 }1199 }1200 }