/
githubmirror
/
PowerShell
Обзор
Документация
Войти
/
githubmirror
/
PowerShell
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/System.Management.Automation/engine/Attributes.cs
2 364 строки
92 KB
xtqqczze
Fix `IDE0049` for `object` in `System.Management.Automation`. Part 1 (#25923)
17 окт 2025, 13:30
Не верифицирован
17 окт 2025, 13:30
907c3b1
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Management.Automation.Security; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace System.Management.Automation.Internal { /// <summary> /// Serves as the base class for Metadata attributes. /// </summary> /// <remarks> /// PSSnapins may not create custom attributes derived directly from <see cref="CmdletMetadataAttribute"/>, /// since it has no public constructor. Only the public subclasses <see cref="ValidateArgumentsAttribute"/> /// and <see cref="ArgumentTransformationAttribute"/> are available. /// </remarks> /// <seealso cref="CmdletMetadataAttribute"/> /// <seealso cref="ValidateArgumentsAttribute"/> /// <seealso cref="ArgumentTransformationAttribute"/> [AttributeUsage(AttributeTargets.All)] public abstract class CmdletMetadataAttribute : Attribute { /// <summary> /// Default constructor. /// </summary> internal CmdletMetadataAttribute() { } } /// <summary> /// Serves as the base class for Metadata attributes that serve as guidance to the parser and parameter binder. /// </summary> /// <remarks> /// PSSnapins may not create custom attributes derived from <see cref="ParsingBaseAttribute"/>, since it /// has no public constructor. Only the sealed public subclasses <see cref="ParameterAttribute"/> and /// <see cref="AliasAttribute"/> are available. /// </remarks> /// <seealso cref="ParsingBaseAttribute"/> /// <seealso cref="ParameterAttribute"/> /// <seealso cref="AliasAttribute"/> [AttributeUsage(AttributeTargets.All)] public abstract class ParsingBaseAttribute : CmdletMetadataAttribute { /// <summary> /// Constructor with no parameters. /// </summary> internal ParsingBaseAttribute() { } } } namespace System.Management.Automation { #region Base Metadata Classes /// <summary> /// Serves as the base class for Validate attributes that validate parameter arguments. /// </summary> /// <remarks> /// Argument validation attributes can be attached to <see cref="Cmdlet"/> and /// <see cref="Provider.CmdletProvider"/> parameters to ensure that the Cmdlet or CmdletProvider will /// not be invoked with invalid values of the parameter. Existing validation attributes include /// <see cref="ValidateCountAttribute"/>, /// <see cref="ValidateNotNullAttribute"/>, /// <see cref="ValidateNotNullOrEmptyAttribute"/>, /// <see cref="ValidateArgumentsAttribute"/>, /// <see cref="ValidateLengthAttribute"/>, /// <see cref="ValidateRangeAttribute"/>, /// <see cref="ValidatePatternAttribute"/>, and /// <see cref="ValidateSetAttribute"/>. /// PSSnapins wishing to create custom argument validation attributes should derive from /// <see cref="ValidateArgumentsAttribute"/> and override the /// <see cref="ValidateArgumentsAttribute.Validate"/> abstract method, after which they can apply the /// attribute to their parameters. /// <see cref="ValidateArgumentsAttribute"/> validates the argument as a whole. If the argument value may /// be an enumerable, you can derive from <see cref="ValidateEnumeratedArgumentsAttribute"/> /// which will take care of unrolling the enumerable and validate each element individually. /// It is also recommended to override <see cref="object.ToString"/> to return a readable string /// similar to the attribute declaration, for example "[ValidateRangeAttribute(5,10)]". /// If this attribute is applied to a string parameter, the string command argument will be validated. /// If this attribute is applied to a string[] parameter, the string[] command argument will be validated. /// </remarks> /// <seealso cref="ValidateEnumeratedArgumentsAttribute"/> /// <seealso cref="ValidateCountAttribute"/> /// <seealso cref="ValidateNotNullAttribute"/> /// <seealso cref="ValidateNotNullOrEmptyAttribute"/> /// <seealso cref="ValidateArgumentsAttribute"/> /// <seealso cref="ValidateLengthAttribute"/> /// <seealso cref="ValidateRangeAttribute"/> /// <seealso cref="ValidatePatternAttribute"/> /// <seealso cref="ValidateSetAttribute"/> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public abstract class ValidateArgumentsAttribute : CmdletMetadataAttribute { /// <summary> /// Verify that the value of <paramref name="arguments"/> is valid. /// </summary> /// <param name="arguments">Argument value to validate.</param> /// <param name="engineIntrinsics"> /// The engine APIs for the context under which the prerequisite is being evaluated. /// </param> /// <exception cref="ValidationMetadataException">Should be thrown for any validation failure.</exception> protected abstract void Validate(object arguments, EngineIntrinsics engineIntrinsics); /// <summary> /// Method that the command processor calls for data validate processing. /// </summary> /// <param name="o">Object to validate.</param> /// <param name="engineIntrinsics"> /// The engine APIs for the context under which the prerequisite is being evaluated. /// </param> /// <returns>True if the validation succeeded.</returns> /// <exception cref="ValidationMetadataException"> /// Whenever any exception occurs during data validation. /// Additionally, all the system exceptions are wrapped in ValidationMetadataException. /// </exception> /// <exception cref="ArgumentException">For invalid arguments.</exception> internal void InternalValidate(object o, EngineIntrinsics engineIntrinsics) => Validate(o, engineIntrinsics); /// <summary> /// Initializes a new instance of a class derived from <see cref="ValidateArgumentsAttribute"/>. /// </summary> protected ValidateArgumentsAttribute() { } } /// <summary> /// A variant of <see cref="ValidateArgumentsAttribute"/> which unrolls enumeration values and validates /// each element individually. /// </summary> /// <remarks> /// <see cref="ValidateEnumeratedArgumentsAttribute"/> is like <see cref="ValidateArgumentsAttribute"/>, /// except that if the argument value is enumerable, <see cref="ValidateEnumeratedArgumentsAttribute"/> /// will unroll the enumeration and validate each item individually. /// Existing enumerated validation attributes include /// <see cref="ValidateLengthAttribute"/>, /// <see cref="ValidateRangeAttribute"/>, /// <see cref="ValidatePatternAttribute"/>, and /// <see cref="ValidateSetAttribute"/>. /// PSSnapins wishing to create custom enumerated argument validation attributes should derive from /// <seealso cref="ValidateEnumeratedArgumentsAttribute"/> and override the /// <seealso cref="ValidateEnumeratedArgumentsAttribute.ValidateElement"/> /// abstract method, after which they can apply the attribute to their parameters. /// It is also recommended to override <see cref="object.ToString"/> to return a readable string /// similar to the attribute declaration, for example "[ValidateRangeAttribute(5,10)]". /// If this attribute is applied to a string parameter, the string command argument will be validated. /// If this attribute is applied to a string[] parameter, each string command argument will be validated. /// </remarks> /// <seealso cref="ValidateArgumentsAttribute"/> /// <seealso cref="ValidateLengthAttribute"/> /// <seealso cref="ValidateRangeAttribute"/> /// <seealso cref="ValidatePatternAttribute"/> /// <seealso cref="ValidateSetAttribute"/> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public abstract class ValidateEnumeratedArgumentsAttribute : ValidateArgumentsAttribute { /// <summary> /// Initializes a new instance of a class derived from <see cref="ValidateEnumeratedArgumentsAttribute"/>. /// </summary> protected ValidateEnumeratedArgumentsAttribute() : base() { } /// <summary> /// Abstract method to be overridden by subclasses, implementing the validation of each parameter argument. /// </summary> /// <remarks> /// Validate that the value of <paramref name="element"/> is valid, and throw /// <see cref="ValidationMetadataException"/> if it is invalid. /// </remarks> /// <param name="element">One of the parameter arguments.</param> /// <exception cref="ValidationMetadataException">Should be thrown for any validation failure.</exception> protected abstract void ValidateElement(object element); /// <summary> /// Calls ValidateElement in each element in the enumeration argument value. /// </summary> /// <param name="arguments">Object to validate.</param> /// <param name="engineIntrinsics"> /// The engine APIs for the context under which the prerequisite is being evaluated. /// </param> /// <remarks> /// PSSnapins should override <see cref="ValidateElement"/> instead. /// </remarks> /// <exception cref="ValidationMetadataException">Should be thrown for any validation failure.</exception> protected sealed override void Validate(object arguments, EngineIntrinsics engineIntrinsics) { if (LanguagePrimitives.IsNull(arguments)) { throw new ValidationMetadataException( "ArgumentIsEmpty", null, Metadata.ValidateNotNullOrEmptyCollectionFailure); } var enumerator = _getEnumeratorSite.Target.Invoke(_getEnumeratorSite, arguments); if (enumerator == null) { ValidateElement(arguments); return; } // arguments is IEnumerator while (enumerator.MoveNext()) { ValidateElement(enumerator.Current); } enumerator.Reset(); } private readonly CallSite<Func<CallSite, object, IEnumerator>> _getEnumeratorSite = CallSite<Func<CallSite, object, IEnumerator>>.Create(PSEnumerableBinder.Get()); } #endregion Base Metadata Classes #region Misc Attributes /// <summary> /// To specify RunAs behavior for the class /// /// </summary> public enum DSCResourceRunAsCredential { /// <summary>Default is same as optional.</summary> Default, /// <summary> /// PsDscRunAsCredential can not be used for this DSC Resource. /// </summary> NotSupported, /// <summary> /// PsDscRunAsCredential is mandatory for resource. /// </summary> Mandatory, /// <summary> /// PsDscRunAsCredential can or can not be specified. /// </summary> Optional = Default, } /// <summary> /// Indicates the class defines a DSC resource. /// </summary> [AttributeUsage(AttributeTargets.Class)] public class DscResourceAttribute : CmdletMetadataAttribute { /// <summary> /// To specify RunAs Behavior for the resource. /// </summary> public DSCResourceRunAsCredential RunAsCredential { get; set; } } /// <summary> /// When specified on a property or field of a DSC Resource, the property /// can or must be specified in a configuration, unless it is marked /// <see cref="DscPropertyAttribute.NotConfigurable"/>, in which case it is /// returned by the Get() method of the resource. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public class DscPropertyAttribute : CmdletMetadataAttribute { /// <summary> /// Indicates the property is a required key property for a DSC resource. /// </summary> public bool Key { get; set; } /// <summary> /// Indicates the property is a required property for a DSC resource. /// </summary> public bool Mandatory { get; set; } /// <summary> /// Indicates the property is not a parameter to the DSC resource, but the /// property will contain a value after the Get() method of the resource is called. /// </summary> public bool NotConfigurable { get; set; } } /// <summary> /// Indication the configuration is for local configuration manager, also known as meta configuration. /// </summary> [AttributeUsage(AttributeTargets.Class)] public class DscLocalConfigurationManagerAttribute : CmdletMetadataAttribute { } /// <summary> /// Contains information about a cmdlet's metadata. /// </summary> [AttributeUsage(AttributeTargets.Class)] public abstract class CmdletCommonMetadataAttribute : CmdletMetadataAttribute { /// <summary> /// Gets or sets the cmdlet default parameter set. /// </summary> public string DefaultParameterSetName { get; set; } /// <summary> /// Gets or sets a Boolean value that indicates the Cmdlet supports ShouldProcess. By default /// the value is false, meaning the cmdlet doesn't support ShouldProcess. /// </summary> public bool SupportsShouldProcess { get; set; } = false; /// <summary> /// Gets or sets a Boolean value that indicates the Cmdlet supports Paging. By default /// the value is false, meaning the cmdlet doesn't support Paging. /// </summary> public bool SupportsPaging { get; set; } = false; /// <summary> /// Gets or sets a Boolean value that indicates the Cmdlet supports Transactions. By default /// the value is false, meaning the cmdlet doesn't support Transactions. /// </summary> public bool SupportsTransactions { get { return _supportsTransactions; } set { #if !CORECLR _supportsTransactions = value; #else // Disable 'SupportsTransactions' in CoreCLR // No transaction supported on CSS due to the lack of System.Transactions namespace _supportsTransactions = false; #endif } } private bool _supportsTransactions = false; private ConfirmImpact _confirmImpact = ConfirmImpact.Medium; /// <summary> /// Gets or sets a ConfirmImpact value that indicates the "destructiveness" of the operation /// and when it should be confirmed. This should only be used when SupportsShouldProcess is /// specified. /// </summary> public ConfirmImpact ConfirmImpact { get => SupportsShouldProcess ? _confirmImpact : ConfirmImpact.None; set => _confirmImpact = value; } /// <summary> /// Gets or sets a HelpUri value that indicates the location of online help. This is used by /// Get-Help to retrieve help content when -Online is specified. /// </summary> [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")] public string HelpUri { get; set; } = string.Empty; /// <summary> /// Gets or sets the RemotingBehavior value that declares how this cmdlet should interact /// with ambient remoting. /// </summary> public RemotingCapability RemotingCapability { get; set; } = RemotingCapability.PowerShell; } /// <summary> /// Identifies a class as a cmdlet and specifies the verb and noun identifying this cmdlet. /// </summary> [AttributeUsage(AttributeTargets.Class)] public sealed class CmdletAttribute : CmdletCommonMetadataAttribute { /// <summary> /// Gets the cmdlet noun. /// </summary> public string NounName { get; } /// <summary> /// Gets the cmdlet verb. /// </summary> public string VerbName { get; } /// <summary> /// Initializes a new instance of the CmdletAttribute class. /// </summary> /// <param name="verbName">Verb for the command.</param> /// <param name="nounName">Noun for the command.</param> /// <exception cref="ArgumentException">For invalid arguments.</exception> public CmdletAttribute(string verbName, string nounName) { // NounName,VerbName have to be Non-Null strings if (string.IsNullOrEmpty(nounName)) { throw PSTraceSource.NewArgumentException(nameof(nounName)); } if (string.IsNullOrEmpty(verbName)) { throw PSTraceSource.NewArgumentException(nameof(verbName)); } NounName = nounName; VerbName = verbName; } } /// <summary> /// Identifies PowerShell script code as behaving like a cmdlet and hence uses cmdlet parameter binding /// instead of script parameter binding. /// </summary> [AttributeUsage(AttributeTargets.Class)] public class CmdletBindingAttribute : CmdletCommonMetadataAttribute { /// <summary> /// When true, the script will auto-generate appropriate parameter metadata to support positional /// parameters if the script hasn't already specified multiple parameter sets or specified positions /// explicitly via the <see cref="ParameterAttribute"/>. /// </summary> public bool PositionalBinding { get; set; } = true; } /// <summary> /// OutputTypeAttribute is used to specify the type of objects output by a cmdlet or script. /// </summary> [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] [SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments")] public sealed class OutputTypeAttribute : CmdletMetadataAttribute { /// <summary> /// Construct the attribute from a <see>System.Type</see> /// </summary> internal OutputTypeAttribute(Type type) { Type = new[] { new PSTypeName(type) }; } /// <summary> /// Construct the attribute from a type name. /// </summary> internal OutputTypeAttribute(string typeName) { Type = new[] { new PSTypeName(typeName) }; } /// <summary> /// Construct the attribute from an array of <see>System.Type</see> /// </summary> /// <param name="type">The types output by the cmdlet.</param> public OutputTypeAttribute(params Type[] type) { if (type?.Length > 0) { Type = new PSTypeName[type.Length]; for (int i = 0; i < type.Length; i++) { Type[i] = new PSTypeName(type[i]); } } else { Type = Array.Empty<PSTypeName>(); } } /// <summary> /// Construct the attribute from an array of names of types. /// </summary> /// <param name="type">The types output by the cmdlet.</param> public OutputTypeAttribute(params string[] type) { if (type?.Length > 0) { Type = new PSTypeName[type.Length]; for (int i = 0; i < type.Length; i++) { Type[i] = new PSTypeName(type[i]); } } else { Type = Array.Empty<PSTypeName>(); } } /// <summary> /// The types specified by the attribute. /// </summary> [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] public PSTypeName[] Type { get; } /// <summary> /// Attributes implemented by a provider can use: /// [OutputType(ProviderCmdlet='cmdlet', typeof(...))] /// To specify the provider specific objects returned for a given cmdlet. /// </summary> public string ProviderCmdlet { get; set; } /// <summary> /// The list of parameter sets this OutputType specifies. /// </summary> [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ParameterSetName { get => _parameterSetName ??= new[] { ParameterAttribute.AllParameterSets }; set => _parameterSetName = value; } private string[] _parameterSetName; } /// <summary> /// This attribute is used on a dynamic assembly to mark it as one that is used to implement /// a set of classes defined in a PowerShell script. /// </summary> [AttributeUsage(AttributeTargets.Assembly)] public class DynamicClassImplementationAssemblyAttribute : Attribute { /// <summary> /// The (possibly null) path to the file defining this class. /// </summary> public string ScriptFile { get; set; } } #endregion Misc Attributes #region Parsing guidelines Attributes /// <summary> /// Declares an alternative name for a parameter, cmdlet, or function. /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] public sealed class AliasAttribute : ParsingBaseAttribute { internal string[] aliasNames; /// <summary> /// Gets the alias names passed to the constructor. /// </summary> public IList<string> AliasNames { get => this.aliasNames; } /// <summary> /// Initializes a new instance of the AliasAttribute class. /// </summary> /// <param name="aliasNames">The name for this alias.</param> /// <exception cref="ArgumentException">For invalid arguments.</exception> public AliasAttribute(params string[] aliasNames) { if (aliasNames == null) { throw PSTraceSource.NewArgumentNullException(nameof(aliasNames)); } this.aliasNames = aliasNames; } } /// <summary> /// Identifies parameters to Cmdlets. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] public sealed class ParameterAttribute : ParsingBaseAttribute { /// <summary> /// ParameterSetName referring to all ParameterSets. /// </summary> public const string AllParameterSets = "__AllParameterSets"; /// <summary> /// Initializes a new instance of the ParameterAttribute class. /// </summary> public ParameterAttribute() { } /// <summary> /// Initializes a new instance that is associated with an experimental feature. /// </summary> public ParameterAttribute(string experimentName, ExperimentAction experimentAction) { ExperimentalAttribute.ValidateArguments(experimentName, experimentAction); ExperimentName = experimentName; ExperimentAction = experimentAction; } private string _parameterSetName = ParameterAttribute.AllParameterSets; private string _helpMessage; private string _helpMessageBaseName; private string _helpMessageResourceId; #region Experimental Feature Related Properties /// <summary> /// Gets the name of the experimental feature this attribute is associated with. /// </summary> public string ExperimentName { get; } /// <summary> /// Gets the action for the engine to take when the experimental feature is enabled. /// </summary> public ExperimentAction ExperimentAction { get; } internal bool ToHide => EffectiveAction == ExperimentAction.Hide; internal bool ToShow => EffectiveAction == ExperimentAction.Show; /// <summary> /// Gets the effective action to take at run time. /// </summary> private ExperimentAction EffectiveAction { get { if (_effectiveAction == ExperimentAction.None) { _effectiveAction = ExperimentalFeature.GetActionToTake(ExperimentName, ExperimentAction); } return _effectiveAction; } } private ExperimentAction _effectiveAction = default(ExperimentAction); #endregion /// <summary> /// Gets or sets the parameter position. /// If not set, the parameter is named. /// </summary> public int Position { get; set; } = int.MinValue; /// <summary> /// Gets or sets the name of the parameter set this parameter belongs to. /// When it is not specified, <see cref="ParameterAttribute.AllParameterSets"/> is assumed. /// </summary> public string ParameterSetName { get => _parameterSetName; set => _parameterSetName = string.IsNullOrEmpty(value) ? ParameterAttribute.AllParameterSets : value; } /// <summary> /// Gets or sets a flag specifying if this parameter is Mandatory. /// When it is not specified, false is assumed and the parameter is considered optional. /// </summary> public bool Mandatory { get; set; } = false; /// <summary> /// Gets or sets a flag that specifies that this parameter can take values from the incoming pipeline /// object. /// When it is not specified, false is assumed. /// </summary> public bool ValueFromPipeline { get; set; } /// <summary> /// Gets or sets a flag that specifies that this parameter can take values from a property in the /// incoming pipeline object with the same name as the parameter or an alias of the parameter. /// When it is not specified, false is assumed. /// </summary> public bool ValueFromPipelineByPropertyName { get; set; } /// <summary> /// Gets or sets a flag that specifies that the remaining command line parameters should be /// associated with this parameter in the form of an array. /// When it is not specified, false is assumed. /// </summary> public bool ValueFromRemainingArguments { get; set; } = false; /// <summary> /// Gets or sets a short description for this parameter, suitable for presentation as a tool tip. /// </summary> /// <exception cref="ArgumentException">For a null or empty value when setting.</exception> public string HelpMessage { get => _helpMessage; set { if (string.IsNullOrEmpty(value)) { throw PSTraceSource.NewArgumentException(nameof(HelpMessage)); } _helpMessage = value; } } /// <summary> /// Gets or sets the base name of the resource for a help message. /// When this field is specified, HelpMessageResourceId must also be specified. /// </summary> /// <exception cref="ArgumentException">For a null or empty value when setting.</exception> public string HelpMessageBaseName { get => _helpMessageBaseName; set { if (string.IsNullOrEmpty(value)) { throw PSTraceSource.NewArgumentException(nameof(HelpMessageBaseName)); } _helpMessageBaseName = value; } } /// <summary> /// Gets or sets the Id of the resource for a help message. /// When this field is specified, HelpMessageBaseName must also be specified. /// </summary> /// <exception cref="ArgumentException">For a null or empty value when setting.</exception> public string HelpMessageResourceId { get => _helpMessageResourceId; set { if (string.IsNullOrEmpty(value)) { throw PSTraceSource.NewArgumentException(nameof(HelpMessageResourceId)); } _helpMessageResourceId = value; } } /// <summary> /// Indicates that this parameter should not be shown to the user in this like intellisense /// This is primarily to be used in functions that are implementing the logic for dynamic keywords. /// </summary> public bool DontShow { get; set; } } /// <summary> /// Specifies PSTypeName of a cmdlet or function parameter. /// </summary> /// <remarks> /// This attribute is used to restrict the type name of the parameter, when the type goes beyond the .NET type system. /// For example one could say: [PSTypeName("System.Management.ManagementObject#root\cimv2\Win32_Process")] /// to only allow Win32_Process objects to be bound to the parameter. /// </remarks> [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] public class PSTypeNameAttribute : Attribute { /// <summary> /// </summary> public string PSTypeName { get; } /// <summary> /// Creates a new PSTypeNameAttribute. /// </summary> /// <param name="psTypeName"></param> [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] public PSTypeNameAttribute(string psTypeName) { if (string.IsNullOrEmpty(psTypeName)) { throw PSTraceSource.NewArgumentException(nameof(psTypeName)); } this.PSTypeName = psTypeName; } } /// <summary> /// Specifies that a parameter supports wildcards. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class SupportsWildcardsAttribute : ParsingBaseAttribute { } /// <summary> /// Specify a default value and/or help comment for a command parameter. This attribute /// does not have any semantic meaning, it is simply an aid to tools to make it simpler /// to know the true default value of a command parameter (which may or may not have /// any correlation with, e.g., the backing store of the Parameter's property or field. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class PSDefaultValueAttribute : ParsingBaseAttribute { /// <summary> /// Specify the default value of a command parameter. The PowerShell engine does not /// use this value in any way, it exists for other tools that want to reflect on cmdlets. /// </summary> public object Value { get; set; } /// <summary> /// Specify the help string for the default value of a command parameter. /// </summary> public string Help { get; set; } } /// <summary> /// Specify that the member is hidden for the purposes of cmdlets like Get-Member and that the /// member is not displayed by default by Format-* cmdlets. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Event)] public sealed class HiddenAttribute : ParsingBaseAttribute { } #endregion Parsing guidelines Attributes #region Data validate Attributes /// <summary> /// Validates that the length of each parameter argument's Length falls in the range specified by /// <see cref="MinLength"/> and <see cref="MaxLength"/>. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class ValidateLengthAttribute : ValidateEnumeratedArgumentsAttribute { /// <summary> /// Gets the attribute's minimum length. /// </summary> public int MinLength { get; } /// <summary> /// Gets the attribute's maximum length. /// </summary> public int MaxLength { get; } /// <summary> /// Validates that the length of each parameter argument's Length falls in the range specified /// by <see cref="MinLength"/> and <see cref="MaxLength"/>. /// </summary> /// <param name="element">Object to validate.</param> /// <exception cref="ValidationMetadataException">If <paramref name="element"/> is not a string /// with length between minLength and maxLength</exception> /// <exception cref="ArgumentException">For invalid arguments.</exception> protected override void ValidateElement(object element) { if (element is not string objectString) { throw new ValidationMetadataException( "ValidateLengthNotString", null, Metadata.ValidateLengthNotString); } int len = objectString.Length; if (len < MinLength) { throw new ValidationMetadataException( "ValidateLengthMinLengthFailure", null, Metadata.ValidateLengthMinLengthFailure, MinLength, len); } if (len > MaxLength) { throw new ValidationMetadataException( "ValidateLengthMaxLengthFailure", null, Metadata.ValidateLengthMaxLengthFailure, MaxLength, len); } } /// <summary> /// Initializes a new instance of the <see cref="ValidateLengthAttribute"/> class. /// </summary> /// <param name="minLength">Minimum required length.</param> /// <param name="maxLength">Maximum required length.</param> /// <exception cref="ArgumentOutOfRangeException">For invalid arguments.</exception> /// <exception cref="ValidationMetadataException">If maxLength is less than minLength.</exception> public ValidateLengthAttribute(int minLength, int maxLength) : base() { if (minLength < 0) { throw PSTraceSource.NewArgumentOutOfRangeException(nameof(minLength), minLength); } if (maxLength <= 0) { throw PSTraceSource.NewArgumentOutOfRangeException(nameof(maxLength), maxLength); } if (maxLength < minLength) { throw new ValidationMetadataException( "ValidateLengthMaxLengthSmallerThanMinLength", null, Metadata.ValidateLengthMaxLengthSmallerThanMinLength); } MinLength = minLength; MaxLength = maxLength; } } /// <summary> /// Predefined range kind to use with ValidateRangeAttribute. /// </summary> public enum ValidateRangeKind { /// <summary> /// Range is greater than 0. /// </summary> Positive, /// <summary> /// Range is greater than or equal to 0. /// </summary> NonNegative, /// <summary> /// Range is less than 0. /// </summary> Negative, /// <summary> /// Range is less than or equal to 0. /// </summary> NonPositive } /// <summary> /// Validates that each parameter argument falls in the range specified by <see cref="MinRange"/> /// and <see cref="MaxRange"/>. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class ValidateRangeAttribute : ValidateEnumeratedArgumentsAttribute { /// <summary> /// Gets the attribute's minimum range. /// </summary> public object MinRange { get; } private readonly IComparable _minComparable; /// <summary> /// Gets the attribute's maximum range. /// </summary> public object MaxRange { get; } private readonly IComparable _maxComparable; /// <summary> /// The range values and the value to validate will all be converted to the promoted type. /// If minRange and maxRange are the same type, /// </summary> private readonly Type _promotedType; /// <summary> /// Gets the name of the predefined range. /// </summary> internal ValidateRangeKind? RangeKind { get => _rangeKind; } private readonly ValidateRangeKind? _rangeKind; /// <summary> /// Validates that each parameter argument falls in the range specified by <see cref="MinRange"/> /// and <see cref="MaxRange"/>. /// </summary> /// <param name="element">Object to validate.</param> /// <exception cref="ValidationMetadataException"> /// Thrown if the object to be validated does not implement <see cref="IComparable"/>, /// if the element type is not the same as MinRange/MaxRange, or if the element is not between /// MinRange and MaxRange. /// </exception> protected override void ValidateElement(object element) { if (element == null) { throw new ValidationMetadataException( "ArgumentIsEmpty", null, Metadata.ValidateNotNullFailure); } var o = element as PSObject; if (o != null) { element = o.BaseObject; } if (_rangeKind.HasValue) { ValidateRange(element, (ValidateRangeKind)_rangeKind); } else { ValidateRange(element); } } /// <summary> /// Initializes a new instance of the <see cref="ValidateRangeAttribute"/> class. /// </summary> /// <param name="minRange">Minimum value of the range allowed.</param> /// <param name="maxRange">Maximum value of the range allowed.</param> /// <exception cref="ArgumentNullException">For invalid arguments.</exception> /// <exception cref="ValidationMetadataException"> /// if <paramref name="maxRange"/> has a different type than <paramref name="minRange"/> /// if <paramref name="maxRange"/> is smaller than <paramref name="minRange"/> /// if <paramref name="maxRange"/>, <paramref name="minRange"/> are not <see cref="IComparable"/> /// </exception> public ValidateRangeAttribute(object minRange, object maxRange) : base() { if (minRange == null) { throw PSTraceSource.NewArgumentNullException(nameof(minRange)); } if (maxRange == null) { throw PSTraceSource.NewArgumentNullException(nameof(maxRange)); } if (maxRange.GetType() != minRange.GetType()) { bool failure = true; _promotedType = GetCommonType(minRange.GetType(), maxRange.GetType()); if (_promotedType != null) { if (LanguagePrimitives.TryConvertTo(minRange, _promotedType, out object minResultValue) && LanguagePrimitives.TryConvertTo(maxRange, _promotedType, out object maxResultValue)) { minRange = minResultValue; maxRange = maxResultValue; failure = false; } } if (failure) { throw new ValidationMetadataException( "MinRangeNotTheSameTypeOfMaxRange", null, Metadata.ValidateRangeMinRangeMaxRangeType, minRange.GetType().Name, maxRange.GetType().Name); } } else { _promotedType = minRange.GetType(); } // minRange and maxRange have the same type, so we just need to check one of them _minComparable = minRange as IComparable; if (_minComparable == null) { throw new ValidationMetadataException( "MinRangeNotIComparable", null, Metadata.ValidateRangeNotIComparable); } _maxComparable = maxRange as IComparable; Diagnostics.Assert(_maxComparable != null, "maxComparable comes from a type that is IComparable"); // Thanks to the IComparable test above this will not throw. They have the same type and are IComparable. if (_minComparable.CompareTo(maxRange) > 0) { throw new ValidationMetadataException( "MaxRangeSmallerThanMinRange", null, Metadata.ValidateRangeMaxRangeSmallerThanMinRange); } MinRange = minRange; MaxRange = maxRange; } /// <summary> /// Initializes a new instance of the <see cref="ValidateRangeAttribute"/> class. /// This constructor uses a predefined <see cref="ValidateRangeKind"/>. /// </summary> public ValidateRangeAttribute(ValidateRangeKind kind) : base() { _rangeKind = kind; } private static void ValidateRange(object element, ValidateRangeKind rangeKind) { if (element is TimeSpan ts) { ValidateTimeSpanRange(ts, rangeKind); return; } Type commonType = GetCommonType(typeof(int), element.GetType()); if (commonType == null) { throw new ValidationMetadataException( "ValidationRangeElementType", innerException: null, Metadata.ValidateRangeElementType, element.GetType().Name, nameof(Int32)); } object resultValue; IComparable dynamicZero = 0; if (LanguagePrimitives.TryConvertTo(element, commonType, out resultValue)) { element = resultValue; if (LanguagePrimitives.TryConvertTo(0, commonType, out resultValue)) { dynamicZero = (IComparable)resultValue; } } else { throw new ValidationMetadataException( "ValidationRangeElementType", null, Metadata.ValidateRangeElementType, element.GetType().Name, commonType.Name); } switch (rangeKind) { case ValidateRangeKind.Positive: if (dynamicZero.CompareTo(element) >= 0) { throw new ValidationMetadataException( "ValidateRangePositiveFailure", null, Metadata.ValidateRangePositiveFailure, element.ToString()); } break; case ValidateRangeKind.NonNegative: if (dynamicZero.CompareTo(element) > 0) { throw new ValidationMetadataException( "ValidateRangeNonNegativeFailure", null, Metadata.ValidateRangeNonNegativeFailure, element.ToString()); } break; case ValidateRangeKind.Negative: if (dynamicZero.CompareTo(element) <= 0) { throw new ValidationMetadataException( "ValidateRangeNegativeFailure", null, Metadata.ValidateRangeNegativeFailure, element.ToString()); } break; case ValidateRangeKind.NonPositive: if (dynamicZero.CompareTo(element) < 0) { throw new ValidationMetadataException( "ValidateRangeNonPositiveFailure", null, Metadata.ValidateRangeNonPositiveFailure, element.ToString()); } break; } } private void ValidateRange(object element) { // MinRange and MaxRange have the same type, so we just need to compare to one of them. if (element.GetType() != _promotedType) { if (LanguagePrimitives.TryConvertTo(element, _promotedType, out object resultValue)) { element = resultValue; } else { throw new ValidationMetadataException( "ValidationRangeElementType", null, Metadata.ValidateRangeElementType, element.GetType().Name, MinRange.GetType().Name); } } // They are the same type and are all IComparable, so this should not throw if (_minComparable.CompareTo(element) > 0) { throw new ValidationMetadataException( "ValidateRangeTooSmall", null, Metadata.ValidateRangeSmallerThanMinRangeFailure, element.ToString(), MinRange.ToString()); } if (_maxComparable.CompareTo(element) < 0) { throw new ValidationMetadataException( "ValidateRangeTooBig", null, Metadata.ValidateRangeGreaterThanMaxRangeFailure, element.ToString(), MaxRange.ToString()); } } private static void ValidateTimeSpanRange(TimeSpan element, ValidateRangeKind rangeKind) { TimeSpan zero = TimeSpan.Zero; switch (rangeKind) { case ValidateRangeKind.Positive: if (zero.CompareTo(element) >= 0) { throw new ValidationMetadataException( "ValidateRangePositiveFailure", null, Metadata.ValidateRangePositiveFailure, element.ToString()); } break; case ValidateRangeKind.NonNegative: if (zero.CompareTo(element) > 0) { throw new ValidationMetadataException( "ValidateRangeNonNegativeFailure", null, Metadata.ValidateRangeNonNegativeFailure, element.ToString()); } break; case ValidateRangeKind.Negative: if (zero.CompareTo(element) <= 0) { throw new ValidationMetadataException( "ValidateRangeNegativeFailure", null, Metadata.ValidateRangeNegativeFailure, element.ToString()); } break; case ValidateRangeKind.NonPositive: if (zero.CompareTo(element) < 0) { throw new ValidationMetadataException( "ValidateRangeNonPositiveFailure", null, Metadata.ValidateRangeNonPositiveFailure, element.ToString()); } break; } } private static Type GetCommonType(Type minType, Type maxType) { Type resultType = null; TypeCode minTypeCode = LanguagePrimitives.GetTypeCode(minType); TypeCode maxTypeCode = LanguagePrimitives.GetTypeCode(maxType); TypeCode opTypeCode = (int)minTypeCode >= (int)maxTypeCode ? minTypeCode : maxTypeCode; if ((int)opTypeCode <= (int)TypeCode.Int32) { resultType = typeof(int); } else if ((int)opTypeCode <= (int)TypeCode.UInt32) { // If one of the operands is signed, we need to promote to double if the value is negative. // We aren't checking the value, so we unconditionally promote to double. resultType = LanguagePrimitives.IsSignedInteger(minTypeCode) || LanguagePrimitives.IsSignedInteger(maxTypeCode) ? typeof(double) : typeof(uint); } else if ((int)opTypeCode <= (int)TypeCode.Int64) { resultType = typeof(long); } else if ((int)opTypeCode <= (int)TypeCode.UInt64) { // If one of the operands is signed, we need to promote to double if the value is negative. // We aren't checking the value, so we unconditionally promote to double. resultType = LanguagePrimitives.IsSignedInteger(minTypeCode) || LanguagePrimitives.IsSignedInteger(maxTypeCode) ? typeof(double) : typeof(ulong); } else if (opTypeCode == TypeCode.Decimal) { resultType = typeof(decimal); } else if (opTypeCode == TypeCode.Single || opTypeCode == TypeCode.Double) { resultType = typeof(double); } return resultType; } /// <summary> /// Returns only the elements that passed the attribute's validation. /// </summary> /// <param name="elementsToValidate">The objects to validate.</param> internal IEnumerable GetValidatedElements(IEnumerable elementsToValidate) { foreach (var el in elementsToValidate) { try { ValidateElement(el); } catch (ValidationMetadataException) { // Element was not in range - drop continue; } yield return el; } } } /// <summary> /// Validates that each parameter argument matches the <see cref="RegexPattern"/>. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class ValidatePatternAttribute : ValidateEnumeratedArgumentsAttribute { /// <summary> /// Gets the Regex pattern to be used in the validation. /// </summary> public string RegexPattern { get; } /// <summary> /// Gets or sets the Regex options to be used in the validation. /// </summary> public RegexOptions Options { get; set; } = RegexOptions.IgnoreCase; /// <summary> /// Gets or sets the custom error message pattern that is displayed to the user. /// The text representation of the object being validated and the validating regex is passed as /// the first and second formatting parameters to the ErrorMessage formatting pattern. /// <c> /// <code> /// [ValidatePattern("\s+", ErrorMessage="The text '{0}' did not pass validation of regex '{1}'")] /// </code> /// </c> /// </summary> public string ErrorMessage { get; set; } /// <summary> /// Validates that each parameter argument matches the RegexPattern. /// </summary> /// <param name="element">Object to validate.</param> /// <exception cref="ValidationMetadataException"> /// If <paramref name="element"/> is not a string that matches the pattern, and for invalid arguments. /// </exception> protected override void ValidateElement(object element) { if (element == null) { throw new ValidationMetadataException( "ArgumentIsEmpty", null, Metadata.ValidateNotNullFailure); } string objectString = element.ToString(); var regex = new Regex(RegexPattern, Options); Match match = regex.Match(objectString); if (!match.Success) { var errorMessageFormat = string.IsNullOrEmpty(ErrorMessage) ? Metadata.ValidatePatternFailure : ErrorMessage; throw new ValidationMetadataException( "ValidatePatternFailure", null, errorMessageFormat, objectString, RegexPattern); } } /// <summary> /// Initializes a new instance of the <see cref="ValidatePatternAttribute"/> class. /// </summary> /// <param name="regexPattern">Pattern string to match.</param> /// <exception cref="ArgumentException">For invalid arguments.</exception> public ValidatePatternAttribute(string regexPattern) { if (string.IsNullOrEmpty(regexPattern)) { throw PSTraceSource.NewArgumentException(nameof(regexPattern)); } RegexPattern = regexPattern; } } /// <summary> /// Class for validating against a script block. /// </summary> public sealed class ValidateScriptAttribute : ValidateEnumeratedArgumentsAttribute { /// <summary> /// Gets or sets the custom error message that is displayed to the user. /// The item being validated and the validating scriptblock is passed as the first and second /// formatting argument. /// <c> /// <code> /// [ValidateScript("$_ % 2", ErrorMessage = "The item '{0}' did not pass validation of script '{1}'")] /// </code> /// </c> /// </summary> public string ErrorMessage { get; set; } /// <summary> /// Gets the scriptblock to be used in the validation. /// </summary> public ScriptBlock ScriptBlock { get; } /// <summary> /// Validates that each parameter argument matches the scriptblock. /// </summary> /// <param name="element">Object to validate.</param> /// <exception cref="ValidationMetadataException">If <paramref name="element"/> is invalid.</exception> protected override void ValidateElement(object element) { if (element == null) { throw new ValidationMetadataException( "ArgumentIsEmpty", null, Metadata.ValidateNotNullFailure); } object result = ScriptBlock.DoInvokeReturnAsIs( useLocalScope: true, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToExternalErrorPipe, dollarUnder: LanguagePrimitives.AsPSObjectOrNull(element), input: AutomationNull.Value, scriptThis: AutomationNull.Value, args: Array.Empty<object>()); if (!LanguagePrimitives.IsTrue(result)) { var errorMessageFormat = string.IsNullOrEmpty(ErrorMessage) ? Metadata.ValidateScriptFailure : ErrorMessage; throw new ValidationMetadataException( "ValidateScriptFailure", null, errorMessageFormat, element, ScriptBlock); } } /// <summary> /// Initializes a new instance of the <see cref="ValidateScriptAttribute"/> class. /// </summary> /// <param name="scriptBlock">Scriptblock to match.</param> /// <exception cref="ArgumentException">For invalid arguments.</exception> public ValidateScriptAttribute(ScriptBlock scriptBlock) { if (scriptBlock == null) { throw PSTraceSource.NewArgumentException(nameof(scriptBlock)); } ScriptBlock = scriptBlock; } } /// <summary> /// Validates that the parameter argument count is in the specified range. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class ValidateCountAttribute : ValidateArgumentsAttribute { /// <summary> /// Gets the minimum length of this attribute. /// </summary> public int MinLength { get; } /// <summary> /// Gets the maximum length of this attribute. /// </summary> public int MaxLength { get; } /// <summary> /// Validates that the parameter argument count is in the specified range. /// </summary> /// <param name="arguments">Object to validate.</param> /// <param name="engineIntrinsics"> /// The engine APIs for the context under which the validation is being evaluated. /// </param> /// <exception cref="ValidationMetadataException"> /// if the element is none of <see cref="ICollection"/>, <see cref="IEnumerable"/>, /// <see cref="IList"/>, <see cref="IEnumerator"/> /// if the element's length is not between <see cref="MinLength"/> and <see cref="MaxLength"/> /// </exception> protected override void Validate(object arguments, EngineIntrinsics engineIntrinsics) { int len = 0; if (arguments == null || arguments == AutomationNull.Value) { // treat a nul list the same as an empty list // with a count of zero. len = 0; } else if (arguments is IList il) { len = il.Count; } else if (arguments is ICollection ic) { len = ic.Count; } else if (arguments is IEnumerable ie) { IEnumerator e = ie.GetEnumerator(); while (e.MoveNext()) { len++; } } else if (arguments is IEnumerator enumerator) { while (enumerator.MoveNext()) { len++; } } else { // No conversion succeeded so throw an exception... throw new ValidationMetadataException( "NotAnArrayParameter", null, Metadata.ValidateCountNotInArray); } if (MinLength == MaxLength && len != MaxLength) { throw new ValidationMetadataException( "ValidateCountExactFailure", null, Metadata.ValidateCountExactFailure, MaxLength, len); } if (len < MinLength || len > MaxLength) { throw new ValidationMetadataException( "ValidateCountMinMaxFailure", null, Metadata.ValidateCountMinMaxFailure, MinLength, MaxLength, len); } } /// <summary> /// Initializes a new instance of the <see cref="ValidateCountAttribute"/> class. /// </summary> /// <param name="minLength">Minimum number of values required.</param> /// <param name="maxLength">Maximum number of values required.</param> /// <exception cref="ArgumentOutOfRangeException">For invalid arguments.</exception> /// <exception cref="ValidationMetadataException"> /// if <paramref name="minLength"/> is greater than <paramref name="maxLength"/> /// </exception> public ValidateCountAttribute(int minLength, int maxLength) { if (minLength < 0) { throw PSTraceSource.NewArgumentOutOfRangeException(nameof(minLength), minLength); } if (maxLength <= 0) { throw PSTraceSource.NewArgumentOutOfRangeException(nameof(maxLength), maxLength); } if (maxLength < minLength) { throw new ValidationMetadataException( "ValidateRangeMaxLengthSmallerThanMinLength", null, Metadata.ValidateCountMaxLengthSmallerThanMinLength); } MinLength = minLength; MaxLength = maxLength; } } /// <summary> /// Optional base class for <see cref="IValidateSetValuesGenerator"/> implementations that want a default /// implementation to cache valid values. /// </summary> public abstract class CachedValidValuesGeneratorBase : IValidateSetValuesGenerator { // Cached valid values. private string[] _validValues; private readonly int _validValuesCacheExpiration; /// <summary> /// Initializes a new instance of the <see cref="CachedValidValuesGeneratorBase"/> class. /// </summary> /// <param name="cacheExpirationInSeconds"> /// Sets a time interval in seconds to reset the <see cref="_validValues"/> dynamic valid values cache. /// </param> protected CachedValidValuesGeneratorBase(int cacheExpirationInSeconds) { _validValuesCacheExpiration = cacheExpirationInSeconds; } /// <summary> /// Abstract method to generate a valid values. /// </summary> public abstract string[] GenerateValidValues(); /// <summary> /// Get a valid values. /// </summary> public string[] GetValidValues() { // Because we have a background task to clear the cache by '_validValues = null' // we use the local variable to exclude a race condition. var validValuesLocal = _validValues; if (validValuesLocal != null) { return validValuesLocal; } var validValuesNoCache = GenerateValidValues(); if (validValuesNoCache == null) { throw new ValidationMetadataException( "ValidateSetGeneratedValidValuesListIsNull", null, Metadata.ValidateSetGeneratedValidValuesListIsNull); } if (_validValuesCacheExpiration > 0) { _validValues = validValuesNoCache; Task.Delay(_validValuesCacheExpiration * 1000).ContinueWith((task) => _validValues = null); } return validValuesNoCache; } } /// <summary> /// Validates that each parameter argument is present in a specified set. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class ValidateSetAttribute : ValidateEnumeratedArgumentsAttribute { // We can use either static '_validValues' or dynamic valid values list generated by instance // of 'validValuesGenerator'. private readonly string[] _validValues; private readonly IValidateSetValuesGenerator validValuesGenerator = null; // The valid values generator cache works across 'ValidateSetAttribute' instances. private static readonly ConcurrentDictionary<Type, IValidateSetValuesGenerator> s_ValidValuesGeneratorCache = new ConcurrentDictionary<Type, IValidateSetValuesGenerator>(); /// <summary> /// Gets or sets the custom error message that is displayed to the user. /// The item being validated and a text representation of the validation set is passed as the /// first and second formatting argument to the <see cref="ErrorMessage"/> formatting pattern. /// <c> /// <code> /// [ValidateSet("A","B","C", ErrorMessage="The item '{0}' is not part of the set '{1}'.") /// </code> /// </c> /// </summary> public string ErrorMessage { get; set; } /// <summary> /// Gets a flag specifying if we should ignore the case when performing string comparison. /// The default is true. /// </summary> public bool IgnoreCase { get; set; } = true; /// <summary> /// Gets the valid values in the set. /// </summary> [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations", Justification = "<Pending>")] public IList<string> ValidValues { get { if (validValuesGenerator == null) { return _validValues; } var validValuesLocal = validValuesGenerator.GetValidValues(); if (validValuesLocal == null) { throw new ValidationMetadataException( "ValidateSetGeneratedValidValuesListIsNull", null, Metadata.ValidateSetGeneratedValidValuesListIsNull); } return validValuesLocal; } } /// <summary> /// Validates that each parameter argument is present in the specified set. /// </summary> /// <param name="element">Object to validate.</param> /// <exception cref="ValidationMetadataException"> /// if element is not in the set /// for invalid argument /// </exception> protected override void ValidateElement(object element) { if (element == null) { throw new ValidationMetadataException( "ArgumentIsEmpty", null, Metadata.ValidateNotNullFailure); } string objString = element.ToString(); foreach (string setString in ValidValues) { if (CultureInfo.InvariantCulture.CompareInfo.Compare( setString, objString, IgnoreCase ? CompareOptions.IgnoreCase : CompareOptions.None) == 0) { return; } } var errorMessageFormat = string.IsNullOrEmpty(ErrorMessage) ? Metadata.ValidateSetFailure : ErrorMessage; throw new ValidationMetadataException( "ValidateSetFailure", null, errorMessageFormat, element.ToString(), SetAsString()); } private string SetAsString() => string.Join(CultureInfo.CurrentUICulture.TextInfo.ListSeparator, ValidValues); /// <summary> /// Initializes a new instance of the <see cref="ValidateSetAttribute"/> class. /// </summary> /// <param name="validValues">List of valid values.</param> /// <exception cref="ArgumentNullException">For null arguments.</exception> /// <exception cref="ArgumentOutOfRangeException">For invalid arguments.</exception> public ValidateSetAttribute(params string[] validValues) { if (validValues == null) { throw PSTraceSource.NewArgumentNullException(nameof(validValues)); } if (validValues.Length == 0) { throw PSTraceSource.NewArgumentOutOfRangeException(nameof(validValues), validValues); } _validValues = validValues; } /// <summary> /// Initializes a new instance of the <see cref="ValidateSetAttribute"/> class. /// Valid values are returned dynamically from a custom class implementing /// <see cref="IValidateSetValuesGenerator"/> /// </summary> /// <param name="valuesGeneratorType"> /// Class that implements the <see cref="IValidateSetValuesGenerator"/> interface. /// </param> /// <exception cref="ArgumentException">For null arguments.</exception> public ValidateSetAttribute(Type valuesGeneratorType) { // We check 'IsNotPublic' because we don't want allow 'Activator.CreateInstance' create an // instance of non-public type. if (!typeof(IValidateSetValuesGenerator).IsAssignableFrom( valuesGeneratorType) || valuesGeneratorType.IsNotPublic) { throw PSTraceSource.NewArgumentException(nameof(valuesGeneratorType)); } // Add a valid values generator to the cache. // We don't cache valid values; we expect that valid values will be cached in the generator. validValuesGenerator = s_ValidValuesGeneratorCache.GetOrAdd( valuesGeneratorType, static (key) => (IValidateSetValuesGenerator)Activator.CreateInstance(key)); } } /// <summary> /// Allows dynamically generate set of values for <see cref="ValidateSetAttribute"/> /// </summary> #nullable enable public interface IValidateSetValuesGenerator { /// <summary> /// Gets valid values. /// </summary> /// <returns>A non-null array of non-null strings.</returns> string[] GetValidValues(); } #nullable restore /// <summary> /// Validates that each parameter argument is Trusted data. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class ValidateTrustedDataAttribute : ValidateArgumentsAttribute { /// <summary> /// Validates that the parameter argument is not untrusted. /// </summary> /// <param name="arguments">Object to validate.</param> /// <param name="engineIntrinsics"> /// The engine APIs for the context under which the validation is being evaluated. /// </param> /// <exception cref="ValidationMetadataException"> /// if the argument is untrusted. /// </exception> protected override void Validate(object arguments, EngineIntrinsics engineIntrinsics) { if (ExecutionContext.HasEverUsedConstrainedLanguage && engineIntrinsics.SessionState.Internal.ExecutionContext.LanguageMode == PSLanguageMode.FullLanguage) { if (ExecutionContext.IsMarkedAsUntrusted(arguments)) { if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) { throw new ValidationMetadataException( "ValidateTrustedDataFailure", null, Metadata.ValidateTrustedDataFailure, arguments); } SystemPolicy.LogWDACAuditMessage( context: null, title: Metadata.WDACParameterArgNotTrustedLogTitle, message: StringUtil.Format(Metadata.WDACParameterArgNotTrustedMessage, arguments), fqid: "ParameterArgumentNotTrusted", dropIntoDebugger: true); } } } } #region Allow /// <summary> /// Allows a NULL as the argument to a mandatory parameter. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class AllowNullAttribute : CmdletMetadataAttribute { /// <summary> /// Initializes a new instance of the <see cref="AllowNullAttribute"/> class. /// </summary> public AllowNullAttribute() { } } /// <summary> /// Allows an empty string as the argument to a mandatory string parameter. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class AllowEmptyStringAttribute : CmdletMetadataAttribute { /// <summary> /// Initializes a new instance of the <see cref="AllowEmptyStringAttribute"/> class. /// </summary> public AllowEmptyStringAttribute() { } } /// <summary> /// Allows an empty collection as the argument to a mandatory collection parameter. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class AllowEmptyCollectionAttribute : CmdletMetadataAttribute { /// <summary> /// Initializes a new instance of the <see cref="AllowEmptyCollectionAttribute"/> class. /// </summary> public AllowEmptyCollectionAttribute() { } } #endregion Allow #region Path validation attributes /// <summary> /// Validates that the path has an approved root drive. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public class ValidateDriveAttribute : ValidateArgumentsAttribute { private readonly string[] _validRootDrives; /// <summary> /// Gets the values in the set. /// </summary> public IList<string> ValidRootDrives { get => _validRootDrives; } /// <summary> /// Initializes a new instance of the <see cref="ValidateDriveAttribute"/> class. /// </summary> /// <param name="validRootDrives">List of approved root drives for path.</param> public ValidateDriveAttribute(params string[] validRootDrives) { if (validRootDrives == null) { throw PSTraceSource.NewArgumentException(nameof(validRootDrives)); } _validRootDrives = validRootDrives; } /// <summary> /// Validates path argument. /// </summary> /// <param name="arguments">Object to validate.</param> /// <param name="engineIntrinsics">Engine intrinsics.</param> protected override void Validate(object arguments, EngineIntrinsics engineIntrinsics) { if (arguments == null) { throw new ValidationMetadataException( "PathArgumentIsEmpty", null, Metadata.ValidateNotNullFailure); } if (arguments is not string path) { throw new ValidationMetadataException( "PathArgumentIsNotValid", null, Metadata.ValidateDrivePathArgNotString); } var resolvedPath = engineIntrinsics.SessionState.Internal.Globber.GetProviderPath( path: path, context: new CmdletProviderContext(engineIntrinsics.SessionState.Internal.ExecutionContext), isTrusted: true, provider: out ProviderInfo providerInfo, drive: out PSDriveInfo driveInfo); string rootDrive = driveInfo.Name; if (string.IsNullOrEmpty(rootDrive)) { throw new ValidationMetadataException( "PathArgumentNoRoot", null, Metadata.ValidateDrivePathNoRoot); } bool rootFound = false; foreach (var validDrive in _validRootDrives) { if (rootDrive.Equals(validDrive, StringComparison.OrdinalIgnoreCase)) { rootFound = true; break; } } if (!rootFound) { throw new ValidationMetadataException( "PathRootInvalid", null, Metadata.ValidateDrivePathFailure, rootDrive, ValidDriveListAsString()); } } private string ValidDriveListAsString() { return string.Join(CultureInfo.CurrentUICulture.TextInfo.ListSeparator, _validRootDrives); } } /// <summary> /// Validates that the path parameter is a User drive. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class ValidateUserDriveAttribute : ValidateDriveAttribute { /// <summary> /// Initializes a new instance of the <see cref="ValidateUserDriveAttribute"/> class. /// </summary> public ValidateUserDriveAttribute() : base(new string[] { "User" }) { } } #endregion #region NULL validation attributes /// <summary> /// Base type of Null Validation attributes. /// </summary> public abstract class NullValidationAttributeBase : ValidateArgumentsAttribute { /// <summary> /// Check if the argument type is a collection. /// </summary> protected bool IsArgumentCollection(Type argumentType, out bool isElementValueType) { isElementValueType = false; var information = new ParameterCollectionTypeInformation(argumentType); switch (information.ParameterCollectionType) { // If 'arguments' is an array, or implement 'IList', or implement 'ICollection<>' // then we continue to check each element of the collection. case ParameterCollectionType.Array: case ParameterCollectionType.IList: case ParameterCollectionType.ICollectionGeneric: Type elementType = information.ElementType; isElementValueType = elementType != null && elementType.IsValueType; return true; default: return false; } } } /// <summary> /// Validates that the parameters's argument is not null. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class ValidateNotNullAttribute : NullValidationAttributeBase { /// <summary> /// Verifies the argument is not null. If the argument is a collection, verifies that each /// element in the collection is not null. /// </summary> /// <param name="arguments">The arguments to verify.</param> /// <param name="engineIntrinsics"> /// The engine APIs for the context under which the validation is being evaluated. /// </param> /// <returns> /// true if the argument is valid. /// </returns> /// <exception cref="ValidationMetadataException"> /// if element is null or a collection with a null element /// </exception> protected override void Validate(object arguments, EngineIntrinsics engineIntrinsics) { if (LanguagePrimitives.IsNull(arguments)) { throw new ValidationMetadataException( "ArgumentIsNull", null, Metadata.ValidateNotNullFailure); } else if (IsArgumentCollection(arguments.GetType(), out bool isElementValueType)) { // If the element of the collection is of value type, then no need to check for null // because a value-type value cannot be null. if (isElementValueType) { return; } IEnumerator enumerator = LanguagePrimitives.GetEnumerator(arguments); while (enumerator.MoveNext()) { object element = enumerator.Current; if (LanguagePrimitives.IsNull(element)) { throw new ValidationMetadataException( "ArgumentIsNull", null, Metadata.ValidateNotNullCollectionFailure); } } } } } /// <summary> /// Validates that the parameters's argument is not null, is not an empty string or a /// string with white-space characters only, and is not an empty collection. /// </summary> public abstract class ValidateNotNullOrAttributeBase : NullValidationAttributeBase { /// <summary> /// Used to check the type of string validation to perform. /// </summary> protected readonly bool _checkWhiteSpace; /// <summary> /// Validates that the parameters's argument is not null, is not an empty string or a /// string with white-space characters only, and is not an empty collection. /// </summary> protected ValidateNotNullOrAttributeBase(bool checkWhiteSpace) { _checkWhiteSpace = checkWhiteSpace; } /// <summary> /// Validates that the parameters's argument is not null, is not an empty string, and is /// not an empty collection. If argument is a collection, each argument is verified. /// It can also validate that the parameters's argument is not a string that consists /// only of white-space characters. /// </summary> /// <param name="arguments">The arguments to verify.</param> /// <param name="engineIntrinsics"> /// The engine APIs for the context under which the validation is being evaluated. /// </param> /// <exception cref="ValidationMetadataException"> /// if the arguments are not valid. /// </exception> protected override void Validate(object arguments, EngineIntrinsics engineIntrinsics) { if (LanguagePrimitives.IsNull(arguments)) { throw new ValidationMetadataException( "ArgumentIsNull", null, Metadata.ValidateNotNullOrEmptyFailure); } else if (arguments is string str) { if (_checkWhiteSpace) { if (string.IsNullOrWhiteSpace(str)) { throw new ValidationMetadataException( "ArgumentIsEmptyOrWhiteSpace", null, Metadata.ValidateNotNullOrWhiteSpaceFailure); } } else if (string.IsNullOrEmpty(str)) { throw new ValidationMetadataException( "ArgumentIsEmpty", null, Metadata.ValidateNotNullOrEmptyFailure); } } else if (IsArgumentCollection(arguments.GetType(), out bool isElementValueType)) { bool isEmpty = true; IEnumerator enumerator = LanguagePrimitives.GetEnumerator(arguments); if (enumerator.MoveNext()) { isEmpty = false; } // If the element of the collection is of value type, then no need to check for null // because a value-type value cannot be null. if (!isEmpty && !isElementValueType) { do { object element = enumerator.Current; if (LanguagePrimitives.IsNull(element)) { throw new ValidationMetadataException( "ArgumentIsNull", null, Metadata.ValidateNotNullOrEmptyCollectionFailure); } if (element is string elementAsString) { if (_checkWhiteSpace) { if (string.IsNullOrWhiteSpace(elementAsString)) { throw new ValidationMetadataException( "ArgumentCollectionContainsEmptyOrWhiteSpace", null, Metadata.ValidateNotNullOrWhiteSpaceCollectionFailure); } } else if (string.IsNullOrEmpty(elementAsString)) { throw new ValidationMetadataException( "ArgumentCollectionContainsEmpty", null, Metadata.ValidateNotNullOrEmptyCollectionFailure); } } } while (enumerator.MoveNext()); } if (isEmpty) { throw new ValidationMetadataException( "ArgumentIsEmpty", null, Metadata.ValidateNotNullOrEmptyCollectionFailure); } } else if (arguments is IDictionary dict) { if (dict.Count == 0) { throw new ValidationMetadataException( "ArgumentIsEmpty", null, Metadata.ValidateNotNullOrEmptyCollectionFailure); } } } } /// <summary> /// Validates that the parameters's argument is not null, is not an empty string, and is /// not an empty collection. If argument is a collection, each argument is verified. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class ValidateNotNullOrEmptyAttribute : ValidateNotNullOrAttributeBase { /// <summary> /// Validates that the parameters's argument is not null, is not an empty string, and is /// not an empty collection. If argument is a collection, each argument is verified. /// </summary> public ValidateNotNullOrEmptyAttribute() : base(checkWhiteSpace: false) { } } /// <summary> /// Validates that the parameters's argument is not null, is not an empty string, is not a string that /// consists only of white-space characters, and is not an empty collection. If argument is a collection, /// each argument is verified. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class ValidateNotNullOrWhiteSpaceAttribute : ValidateNotNullOrAttributeBase { /// <summary> /// Validates that the parameters's argument is not null, is not an empty string, is not a string that /// consists only of white-space characters, and is not an empty collection. If argument is a collection, /// each argument is verified. /// </summary> public ValidateNotNullOrWhiteSpaceAttribute() : base(checkWhiteSpace: true) { } } #endregion NULL validation attributes #endregion Data validate Attributes #region Data Generation Attributes /// <summary> /// Serves as the base class for attributes that perform argument transformation. /// </summary> /// <remarks> /// Argument transformation attributes can be attached to <see cref="Cmdlet"/> and /// <see cref="Provider.CmdletProvider"/> parameters to automatically transform the argument /// value in some fashion. The transformation might change the object, convert the type, or /// even load a file or AD object based on the name. Existing argument transformation attributes /// include <see cref="ArgumentTypeConverterAttribute"/>. /// Custom argument transformation attributes should derive from /// <see cref="ArgumentTransformationAttribute"/> and override the /// <see cref="ArgumentTransformationAttribute.Transform"/> abstract method, after which they /// can apply the attribute to their parameters. /// It is also recommended to override <see cref="object.ToString"/> to return a readable /// string similar to the attribute declaration, for example "[ValidateRangeAttribute(5,10)]". /// If multiple transformations are defined on a parameter, they will be invoked in series, /// each getting the output of the previous transformation. /// </remarks> /// <seealso cref="ArgumentTypeConverterAttribute"/> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public abstract class ArgumentTransformationAttribute : CmdletMetadataAttribute { /// <summary> /// Initializes a new instance of the <see cref="ArgumentTransformationAttribute"/> class. /// </summary> protected ArgumentTransformationAttribute() { } /// <summary> /// Method that will be overridden by the subclasses to transform the <paramref name="inputData"/> /// parameter argument into some other object that will be used for the parameter's value. /// </summary> /// <param name="engineIntrinsics"> /// The engine APIs for the context under which the transformation is being made. /// </param> /// <param name="inputData">Parameter argument to mutate.</param> /// <returns>The transformed value(s) of <paramref name="inputData"/>.</returns> /// <exception cref="ArgumentException">Should be thrown for invalid arguments.</exception> /// <exception cref="ArgumentTransformationMetadataException"> /// Should be thrown for any problems during transformation. /// </exception> public abstract object Transform(EngineIntrinsics engineIntrinsics, object inputData); /// <summary> /// Transform <paramref name="inputData"/> and track the flow of untrusted object. /// NOTE: All internal handling of <see cref="ArgumentTransformationAttribute"/> should use this method to /// track the trustworthiness of the data input source by default. /// </summary> /// <remarks> /// The default value for <paramref name="trackDataInputSource"/> is true. /// You should stick to the default value for this parameter in most cases so that data input source is /// tracked during the transformation. The only acceptable exception is when this method is used in /// Compiler or Binder where you can generate extra code to track input source when it's necessary. /// This is to minimize the overhead when tracking is not needed. /// </remarks> internal object TransformInternal( EngineIntrinsics engineIntrinsics, object inputData, bool trackDataInputSource = true) { object result = Transform(engineIntrinsics, inputData); if (trackDataInputSource && engineIntrinsics != null) { ExecutionContext.PropagateInputSource( inputData, result, engineIntrinsics.SessionState.Internal.LanguageMode); } return result; } /// <summary> /// The property is only checked when: /// a) The parameter is not mandatory /// b) The argument is null. /// </summary> public virtual bool TransformNullOptionalParameters { get => true; } } #endregion Data Generation Attributes }