MegaBites-AI/Windows-powershell
0372
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.Linq;8using System.Reflection;9 10namespace System.Management.Automation11{12 /// <summary>13 /// The metadata associated with a parameter.14 /// </summary>15 internal class CompiledCommandParameter16 {17 #region ctor18 19 /// <summary>20 /// Constructs an instance of the CompiledCommandAttribute using the specified21 /// runtime-defined parameter.22 /// </summary>23 /// <param name="runtimeDefinedParameter">24 /// A runtime defined parameter that contains the definition of the parameter and its metadata.25 /// </param>26 /// <param name="processingDynamicParameters">27 /// True if dynamic parameters are being processed, or false otherwise.28 /// </param>29 /// <exception cref="ArgumentNullException">30 /// If <paramref name="runtimeDefinedParameter"/> is null.31 /// </exception>32 /// <exception cref="MetadataException">33 /// If the parameter has more than one <see cref="ParameterAttribute">ParameterAttribute</see>34 /// that defines the same parameter-set name.35 /// </exception>36 internal CompiledCommandParameter(RuntimeDefinedParameter runtimeDefinedParameter, bool processingDynamicParameters)37 {38 if (runtimeDefinedParameter == null)39 {40 throw PSTraceSource.NewArgumentNullException(nameof(runtimeDefinedParameter));41 }42 43 this.Name = runtimeDefinedParameter.Name;44 this.Type = runtimeDefinedParameter.ParameterType;45 this.IsDynamic = processingDynamicParameters;46 47 this.CollectionTypeInformation = new ParameterCollectionTypeInformation(runtimeDefinedParameter.ParameterType);48 49 this.CompiledAttributes = new Collection<Attribute>();50 51 this.ParameterSetData = new Dictionary<string, ParameterSetSpecificMetadata>(StringComparer.OrdinalIgnoreCase);52 53 Collection<ValidateArgumentsAttribute> validationAttributes = null;54 Collection<ArgumentTransformationAttribute> argTransformationAttributes = null;55 string[] aliases = null;56 57 // First, process attributes that aren't type conversions58 foreach (Attribute attribute in runtimeDefinedParameter.Attributes)59 {60 if (processingDynamicParameters)61 {62 // When processing dynamic parameters, the attribute list may contain experimental attributes63 // and disabled parameter attributes. We should ignore those attributes.64 // When processing non-dynamic parameters, the experimental attributes and disabled parameter65 // attributes have already been filtered out when constructing the RuntimeDefinedParameter.66 if (attribute is ExperimentalAttribute || attribute is ParameterAttribute param && param.ToHide)67 {68 continue;69 }70 }71 72 if (attribute is not ArgumentTypeConverterAttribute)73 {74 ProcessAttribute(runtimeDefinedParameter.Name, attribute, ref validationAttributes, ref argTransformationAttributes, ref aliases);75 }76 }77 78 // If this is a PSCredential type and they haven't added any argument transformation attributes,79 // add one for credential transformation80 if ((this.Type == typeof(PSCredential)) && argTransformationAttributes == null)81 {82 ProcessAttribute(runtimeDefinedParameter.Name, new CredentialAttribute(), ref validationAttributes, ref argTransformationAttributes, ref aliases);83 }84 85 // Now process type converters86 foreach (var attribute in runtimeDefinedParameter.Attributes.OfType<ArgumentTypeConverterAttribute>())87 {88 ProcessAttribute(runtimeDefinedParameter.Name, attribute, ref validationAttributes, ref argTransformationAttributes, ref aliases);89 }90 91 this.ValidationAttributes = validationAttributes == null92 ? Array.Empty<ValidateArgumentsAttribute>()93 : validationAttributes.ToArray();94 this.ArgumentTransformationAttributes = argTransformationAttributes == null95 ? Array.Empty<ArgumentTransformationAttribute>()96 : argTransformationAttributes.ToArray();97 this.Aliases = aliases == null98 ? Array.Empty<string>()99 : aliases.ToArray();100 }101 102 /// <summary>103 /// Constructs an instance of the CompiledCommandAttribute using the reflection information retrieved104 /// from the enclosing bindable object type.105 /// </summary>106 /// <param name="member">107 /// The member information for the parameter108 /// </param>109 /// <param name="processingDynamicParameters">110 /// True if dynamic parameters are being processed, or false otherwise.111 /// </param>112 /// <exception cref="ArgumentNullException">113 /// If <paramref name="member"/> is null.114 /// </exception>115 /// <exception cref="ArgumentException">116 /// If <paramref name="member"/> is not a field or a property.117 /// </exception>118 /// <exception cref="MetadataException">119 /// If the member has more than one <see cref="ParameterAttribute">ParameterAttribute</see>120 /// that defines the same parameter-set name.121 /// </exception>122 internal CompiledCommandParameter(MemberInfo member, bool processingDynamicParameters)123 {124 if (member == null)125 {126 throw PSTraceSource.NewArgumentNullException(nameof(member));127 }128 129 this.Name = member.Name;130 this.DeclaringType = member.DeclaringType;131 this.IsDynamic = processingDynamicParameters;132 133 var propertyInfo = member as PropertyInfo;134 if (propertyInfo != null)135 {136 this.Type = propertyInfo.PropertyType;137 }138 else139 {140 var fieldInfo = member as FieldInfo;141 if (fieldInfo != null)142 {143 this.Type = fieldInfo.FieldType;144 }145 else146 {147 ArgumentException e =148 PSTraceSource.NewArgumentException(149 nameof(member),150 DiscoveryExceptions.CompiledCommandParameterMemberMustBeFieldOrProperty);151 152 throw e;153 }154 }155 156 this.CollectionTypeInformation = new ParameterCollectionTypeInformation(this.Type);157 this.CompiledAttributes = new Collection<Attribute>();158 this.ParameterSetData = new Dictionary<string, ParameterSetSpecificMetadata>(StringComparer.OrdinalIgnoreCase);159 160 // We do not want to get the inherited custom attributes, only the attributes exposed161 // directly on the member162 163 var memberAttributes = member.GetCustomAttributes(false);164 165 Collection<ValidateArgumentsAttribute> validationAttributes = null;166 Collection<ArgumentTransformationAttribute> argTransformationAttributes = null;167 string[] aliases = null;168 169 foreach (Attribute attr in memberAttributes)170 {171 switch (attr)172 {173 case ExperimentalAttribute _:174 case ParameterAttribute param when param.ToHide:175 break;176 default:177 ProcessAttribute(member.Name, attr, ref validationAttributes, ref argTransformationAttributes, ref aliases);178 break;179 }180 }181 182 this.ValidationAttributes = validationAttributes == null183 ? Array.Empty<ValidateArgumentsAttribute>()184 : validationAttributes.ToArray();185 this.ArgumentTransformationAttributes = argTransformationAttributes == null186 ? Array.Empty<ArgumentTransformationAttribute>()187 : argTransformationAttributes.ToArray();188 this.Aliases = aliases ?? Array.Empty<string>();189 }190 191 #endregion ctor192 193 /// <summary>194 /// Gets the name of the parameter.195 /// </summary>196 internal string Name { get; }197 198 /// <summary>199 /// The PSTypeName from a PSTypeNameAttribute.200 /// </summary>201 internal string PSTypeName { get; private set; }202 203 /// <summary>204 /// Gets the Type information of the attribute.205 /// </summary>206 internal Type Type { get; }207 208 /// <summary>209 /// Gets the Type information of the attribute.210 /// </summary>211 internal Type DeclaringType { get; }212 213 /// <summary>214 /// Gets whether the parameter is a dynamic parameter or not.215 /// </summary>216 internal bool IsDynamic { get; }217 218 /// <summary>219 /// Gets the parameter collection type information.220 /// </summary>221 internal ParameterCollectionTypeInformation CollectionTypeInformation { get; }222 223 /// <summary>224 /// A collection of the attributes found on the member. The attributes have been compiled into225 /// a format that easier to digest by the metadata processor.226 /// </summary>227 internal Collection<Attribute> CompiledAttributes { get; }228 229 /// <summary>230 /// Gets the collection of data generation attributes on this parameter.231 /// </summary>232 internal ArgumentTransformationAttribute[] ArgumentTransformationAttributes { get; }233 234 /// <summary>235 /// Gets the collection of data validation attributes on this parameter.236 /// </summary>237 internal ValidateArgumentsAttribute[] ValidationAttributes { get; }238 239 /// <summary>240 /// Get and private set the obsolete attribute on this parameter.241 /// </summary>242 internal ObsoleteAttribute ObsoleteAttribute { get; private set; }243 244 /// <summary>245 /// If true, null can be bound to the parameter even if the parameter is mandatory.246 /// </summary>247 internal bool AllowsNullArgument { get; private set; }248 249 /// <summary>250 /// If true, null cannot be bound to the parameter (ValidateNotNull251 /// and/or ValidateNotNullOrEmpty has been specified).252 /// </summary>253 internal bool CannotBeNull { get; private set; }254 255 /// <summary>256 /// If true, an empty string can be bound to the string parameter257 /// even if the parameter is mandatory.258 /// </summary>259 internal bool AllowsEmptyStringArgument { get; private set; }260 261 /// <summary>262 /// If true, an empty collection can be bound to the collection/array parameter263 /// even if the parameter is mandatory.264 /// </summary>265 internal bool AllowsEmptyCollectionArgument { get; private set; }266 267 /// <summary>268 /// Gets or sets the value that tells whether this parameter269 /// is for the "all" parameter set.270 /// </summary>271 internal bool IsInAllSets { get; set; }272 273 /// <summary>274 /// Returns true if this parameter is ValueFromPipeline or ValueFromPipelineByPropertyName275 /// in one or more (but not necessarily all) parameter sets.276 /// </summary>277 internal bool IsPipelineParameterInSomeParameterSet { get; private set; }278 279 /// <summary>280 /// Returns true if this parameter is Mandatory in one or more (but not necessarily all) parameter sets.281 /// </summary>282 internal bool IsMandatoryInSomeParameterSet { get; private set; }283 284 /// <summary>285 /// Gets or sets the parameter set flags that map the parameter sets286 /// for this parameter to the parameter set names.287 /// </summary>288 /// <remarks>289 /// This is a bit-field that maps the parameter sets in this parameter290 /// to the parameter sets for the rest of the command.291 /// </remarks>292 internal uint ParameterSetFlags { get; set; }293 294 /// <summary>295 /// A delegate that can set the property.296 /// </summary>297 internal Action<object, object> Setter { get; set; }298 299 /// <summary>300 /// A dictionary of the parameter sets and the parameter set specific data for this parameter.301 /// </summary>302 internal Dictionary<string, ParameterSetSpecificMetadata> ParameterSetData { get; }303 304 /// <summary>305 /// The alias names for this parameter.306 /// </summary>307 internal string[] Aliases { get; }308 309 /// <summary>310 /// Determines if this parameter takes pipeline input for any of the specified311 /// parameter set flags.312 /// </summary>313 /// <param name="validParameterSetFlags">314 /// The flags for the parameter sets to check to see if the parameter takes315 /// pipeline input.316 /// </param>317 /// <returns>318 /// True if the parameter takes pipeline input in any of the specified parameter319 /// sets, or false otherwise.320 /// </returns>321 internal bool DoesParameterSetTakePipelineInput(uint validParameterSetFlags)322 {323 if (!IsPipelineParameterInSomeParameterSet)324 {325 return false;326 }327 328 // Loop through each parameter set the parameter is in to see if that parameter set is329 // still valid. If so, and the parameter takes pipeline input in that parameter set,330 // then return true331 332 foreach (ParameterSetSpecificMetadata parameterSetData in ParameterSetData.Values)333 {334 if ((parameterSetData.IsInAllSets ||335 (parameterSetData.ParameterSetFlag & validParameterSetFlags) != 0) &&336 (parameterSetData.ValueFromPipeline ||337 parameterSetData.ValueFromPipelineByPropertyName))338 {339 return true;340 }341 }342 343 return false;344 }345 346 /// <summary>347 /// Gets the parameter set data for this parameter for the specified parameter set.348 /// </summary>349 /// <param name="parameterSetFlag">350 /// The parameter set to get the parameter set data for.351 /// </param>352 /// <returns>353 /// The parameter set specified data for the specified parameter set.354 /// </returns>355 internal ParameterSetSpecificMetadata GetParameterSetData(uint parameterSetFlag)356 {357 ParameterSetSpecificMetadata result = null;358 359 foreach (ParameterSetSpecificMetadata setData in ParameterSetData.Values)360 {361 // If the parameter is in all sets, then remember the data, but362 // try to find a more specific match363 364 if (setData.IsInAllSets)365 {366 result = setData;367 }368 else369 {370 if ((setData.ParameterSetFlag & parameterSetFlag) != 0)371 {372 result = setData;373 break;374 }375 }376 }377 378 return result;379 }380 381 /// <summary>382 /// Gets the parameter set data for this parameter for the specified parameter sets.383 /// </summary>384 /// <param name="parameterSetFlags">385 /// The parameter sets to get the parameter set data for.386 /// </param>387 /// <returns>388 /// A collection for all parameter set specified data for the parameter sets specified by389 /// the <paramref name="parameterSetFlags"/>.390 /// </returns>391 internal IEnumerable<ParameterSetSpecificMetadata> GetMatchingParameterSetData(uint parameterSetFlags)392 {393 foreach (ParameterSetSpecificMetadata setData in ParameterSetData.Values)394 {395 // If the parameter is in all sets, then remember the data, but396 // try to find a more specific match397 398 if (setData.IsInAllSets)399 {400 yield return setData;401 }402 else403 {404 if ((setData.ParameterSetFlag & parameterSetFlags) != 0)405 {406 yield return setData;407 }408 }409 }410 }411 412 #region helper methods413 414 /// <summary>415 /// Processes the Attribute metadata to generate a CompiledCommandAttribute.416 /// </summary>417 /// <exception cref="MetadataException">418 /// If the attribute is a parameter attribute and another parameter attribute419 /// has been processed with the same parameter-set name.420 /// </exception>421 private void ProcessAttribute(422 string memberName,423 Attribute attribute,424 ref Collection<ValidateArgumentsAttribute> validationAttributes,425 ref Collection<ArgumentTransformationAttribute> argTransformationAttributes,426 ref string[] aliases)427 {428 if (attribute == null)429 return;430 431 CompiledAttributes.Add(attribute);432 433 // Now process the attribute based on it's type434 if (attribute is ParameterAttribute paramAttr)435 {436 ProcessParameterAttribute(memberName, paramAttr);437 return;438 }439 440 ValidateArgumentsAttribute validateAttr = attribute as ValidateArgumentsAttribute;441 if (validateAttr != null)442 {443 validationAttributes ??= new Collection<ValidateArgumentsAttribute>();444 validationAttributes.Add(validateAttr);445 if ((attribute is ValidateNotNullAttribute) || (attribute is ValidateNotNullOrEmptyAttribute))446 {447 this.CannotBeNull = true;448 }449 450 return;451 }452 453 AliasAttribute aliasAttr = attribute as AliasAttribute;454 if (aliasAttr != null)455 {456 if (aliases == null)457 {458 aliases = aliasAttr.aliasNames;459 }460 else461 {462 var prevAliasNames = aliases;463 var newAliasNames = aliasAttr.aliasNames;464 aliases = new string[prevAliasNames.Length + newAliasNames.Length];465 Array.Copy(prevAliasNames, aliases, prevAliasNames.Length);466 Array.Copy(newAliasNames, 0, aliases, prevAliasNames.Length, newAliasNames.Length);467 }468 469 return;470 }471 472 ArgumentTransformationAttribute argumentAttr = attribute as ArgumentTransformationAttribute;473 if (argumentAttr != null)474 {475 argTransformationAttributes ??= new Collection<ArgumentTransformationAttribute>();476 argTransformationAttributes.Add(argumentAttr);477 return;478 }479 480 AllowNullAttribute allowNullAttribute = attribute as AllowNullAttribute;481 if (allowNullAttribute != null)482 {483 this.AllowsNullArgument = true;484 return;485 }486 487 AllowEmptyStringAttribute allowEmptyStringAttribute = attribute as AllowEmptyStringAttribute;488 if (allowEmptyStringAttribute != null)489 {490 this.AllowsEmptyStringArgument = true;491 return;492 }493 494 AllowEmptyCollectionAttribute allowEmptyCollectionAttribute = attribute as AllowEmptyCollectionAttribute;495 if (allowEmptyCollectionAttribute != null)496 {497 this.AllowsEmptyCollectionArgument = true;498 return;499 }500 501 ObsoleteAttribute obsoleteAttr = attribute as ObsoleteAttribute;502 if (obsoleteAttr != null)503 {504 ObsoleteAttribute = obsoleteAttr;505 return;506 }507 508 PSTypeNameAttribute psTypeNameAttribute = attribute as PSTypeNameAttribute;509 if (psTypeNameAttribute != null)510 {511 this.PSTypeName = psTypeNameAttribute.PSTypeName;512 }513 }514 515 /// <summary>516 /// Extracts the data from the ParameterAttribute and creates the member data as necessary.517 /// </summary>518 /// <param name="parameterName">519 /// The name of the parameter.520 /// </param>521 /// <param name="parameter">522 /// The instance of the ParameterAttribute to extract the data from.523 /// </param>524 /// <exception cref="MetadataException">525 /// If a parameter set name has already been declared on this parameter.526 /// </exception>527 private void ProcessParameterAttribute(528 string parameterName,529 ParameterAttribute parameter)530 {531 // If the parameter set name already exists on this parameter and the set name is the default parameter532 // set name, it is an error.533 534 if (ParameterSetData.ContainsKey(parameter.ParameterSetName))535 {536 MetadataException e =537 new MetadataException(538 "ParameterDeclaredInParameterSetMultipleTimes",539 null,540 DiscoveryExceptions.ParameterDeclaredInParameterSetMultipleTimes,541 parameterName,542 parameter.ParameterSetName);543 544 throw e;545 }546 547 if (parameter.ValueFromPipeline || parameter.ValueFromPipelineByPropertyName)548 {549 IsPipelineParameterInSomeParameterSet = true;550 }551 552 if (parameter.Mandatory)553 {554 IsMandatoryInSomeParameterSet = true;555 }556 557 // Construct an instance of the parameter set specific data558 ParameterSetSpecificMetadata parameterSetSpecificData = new ParameterSetSpecificMetadata(parameter);559 ParameterSetData.Add(parameter.ParameterSetName, parameterSetSpecificData);560 }561 562 public override string ToString()563 {564 return Name;565 }566 567 #endregion helper methods568 }569 570 /// <summary>571 /// The types of collections that are supported as parameter types.572 /// </summary>573 internal enum ParameterCollectionType574 {575 NotCollection,576 IList,577 Array,578 ICollectionGeneric579 }580 581 /// <summary>582 /// Contains the collection type information for a parameter.583 /// </summary>584 internal class ParameterCollectionTypeInformation585 {586 /// <summary>587 /// Constructs a parameter collection type information object588 /// which exposes the specified Type's collection type in a589 /// simple way.590 /// </summary>591 /// <param name="type">592 /// The type to determine the collection information for.593 /// </param>594 internal ParameterCollectionTypeInformation(Type type)595 {596 ParameterCollectionType = ParameterCollectionType.NotCollection;597 Diagnostics.Assert(type != null, "Caller to verify type argument");598 599 // NTRAID#Windows OS Bugs-1009284-2004/05/11-JeffJon600 // What other collection types should be supported?601 602 // Look for array types603 604 // NTRAID#Windows Out of Band Releases-906820-2005/09/07605 // According to MSDN, IsSubclassOf returns false if the types are exactly equal.606 // Should this include ==?607 if (type.IsSubclassOf(typeof(Array)))608 {609 ParameterCollectionType = ParameterCollectionType.Array;610 ElementType = type.GetElementType();611 return;612 }613 614 if (typeof(IDictionary).IsAssignableFrom(type))615 {616 return;617 }618 619 Type[] interfaces = type.GetInterfaces();620 if (interfaces.Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>))621 || (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>)))622 {623 return;624 }625 626 bool implementsIList = (type.GetInterface(nameof(IList)) != null);627 628 // Look for class Collection<T>. Collection<T> implements IList, and also IList629 // is more efficient to bind than ICollection<T>. This optimization630 // retrieves the element type so that we can coerce the elements.631 // Otherwise they must already be the right type.632 if (implementsIList && type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Collection<>)))633 {634 ParameterCollectionType = ParameterCollectionType.IList;635 // figure out elementType636 Type[] elementTypes = type.GetGenericArguments();637 Diagnostics.Assert(638 elementTypes.Length == 1,639 "Expected 1 generic argument, got " + elementTypes.Length);640 ElementType = elementTypes[0];641 return;642 }643 644 // Look for interface ICollection<T>. Note that Collection<T>645 // does not implement ICollection<T>, and also, ICollection<T>646 // does not derive from IList. The only way to add elements647 // to an ICollection<T> is via reflected calls to Add(T),648 // but the advantage over plain IList is that we can typecast the elements.649 Type interfaceICollection =650 Array.Find(interfaces, static i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICollection<>));651 if (interfaceICollection != null)652 {653 // We only deal with the first type for which ICollection<T> is implemented654 ParameterCollectionType = ParameterCollectionType.ICollectionGeneric;655 // figure out elementType656 Type[] elementTypes = interfaceICollection.GetGenericArguments();657 Diagnostics.Assert(658 elementTypes.Length == 1,659 "Expected 1 generic argument, got " + elementTypes.Length);660 ElementType = elementTypes[0];661 return;662 }663 664 // Look for IList665 if (implementsIList)666 {667 ParameterCollectionType = ParameterCollectionType.IList;668 // elementType remains null669 return;670 }671 }672 673 /// <summary>674 /// The collection type of the parameter.675 /// </summary>676 internal ParameterCollectionType ParameterCollectionType { get; }677 678 /// <summary>679 /// The type of the elements in the collection.680 /// </summary>681 internal Type ElementType { get; }682 }683}684 