From eed9842534e1d31bcc9b9b61230424d52ca56e97 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Sat, 2 Jun 2018 14:13:54 -0700 Subject: [PATCH] First batch: Basic experimental feature flag and attributes enforced --- .../engine/Attributes.cs | 51 +++ .../CommandCompletion/CompletionAnalysis.cs | 6 +- .../CommandCompletion/CompletionCompleters.cs | 6 +- .../engine/CommandProcessor.cs | 13 +- .../engine/CommandProcessorBase.cs | 21 +- .../engine/CompiledCommandParameter.cs | 25 +- .../ExperimentalFeature.cs | 309 ++++++++++++++++++ .../engine/InitialSessionState.cs | 241 ++++---------- .../engine/Modules/ModuleCmdletBase.cs | 2 +- .../engine/PSConfiguration.cs | 8 + .../engine/PseudoParameters.cs | 31 +- .../engine/ScriptCommandProcessor.cs | 3 +- .../engine/TypeMetadata.cs | 30 +- .../engine/parser/Compiler.cs | 43 ++- .../engine/parser/TypeResolver.cs | 1 + .../engine/parser/ast.cs | 71 ++++ .../engine/runtime/CompiledScriptBlock.cs | 25 ++ .../engine/runtime/Operations/MiscOps.cs | 8 +- .../resources/DiscoveryExceptions.resx | 6 + 19 files changed, 667 insertions(+), 233 deletions(-) create mode 100644 src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index efa3d5c76f..92b3923143 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -620,12 +620,63 @@ namespace System.Management.Automation { } + /// + /// Initializes a new instance that is associated with an experimental feature. + /// + public ParameterAttribute(string experimentName, ExperimentAction experimentAction) + { + if (string.IsNullOrEmpty(experimentName)) + { + throw PSTraceSource.NewArgumentException(nameof(experimentName)); + } + + if (experimentAction == ExperimentAction.None) + { + throw PSTraceSource.NewArgumentException(nameof(experimentAction)); + } + + ExperimentName = experimentName; + ExperimentAction = experimentAction; + } + private string _parameterSetName = ParameterAttribute.AllParameterSets; private string _helpMessage; private string _helpMessageBaseName; private string _helpMessageResourceId; + #region Experimental Feature Related Properties + + /// + /// Name of the experimental feature this attribute is associated with. + /// + public string ExperimentName { get; } + + /// + /// Action for engine to take when the experimental feature is enabled. + /// + public ExperimentAction ExperimentAction { get; } + + internal bool ToHide => EffectiveAction == ExperimentAction.Hide; + internal bool ToShow => EffectiveAction == ExperimentAction.Show; + + /// + /// Effective action to take at run time. + /// + private ExperimentAction EffectiveAction + { + get { + if (_effectiveAction == ExperimentAction.None) + { + _effectiveAction = ExperimentalFeature.GetActionToTake(ExperimentName, ExperimentAction); + } + return _effectiveAction; + } + } + private ExperimentAction _effectiveAction = default(ExperimentAction); + + #endregion + /// /// Gets and sets the parameter position. If not set, the parameter is named. /// diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs index 4f8debce7c..bec70e8b70 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs @@ -1758,8 +1758,10 @@ namespace System.Management.Automation List result = new List(); foreach (PropertyInfo pro in propertyInfos) { - //Ignore TypeId (all attributes inherit it) - if (pro.Name != "TypeId" && (pro.Name.StartsWith(argName, StringComparison.OrdinalIgnoreCase))) + // Ignore getter-only properties, including 'TypeId' (all attributes inherit it). + if (!pro.CanWrite) { continue; } + + if (pro.Name.StartsWith(argName, StringComparison.OrdinalIgnoreCase)) { result.Add(new CompletionResult(pro.Name, pro.Name, CompletionResultType.Property, pro.PropertyType.ToString() + " " + pro.Name)); diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index cf88b76bca..5bac5a9ae8 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -1698,12 +1698,10 @@ namespace System.Management.Automation foreach (ValidateArgumentsAttribute att in parameter.Parameter.ValidationAttributes) { - if (att is ValidateSetAttribute) + if (att is ValidateSetAttribute setAtt) { RemoveLastNullCompletionResult(result); - var setAtt = (ValidateSetAttribute)att; - string wordToComplete = context.WordToComplete; string quote = HandleDoubleAndSingleQuote(ref wordToComplete); @@ -1712,6 +1710,8 @@ namespace System.Management.Automation foreach (string value in setAtt.ValidValues) { + if (value == string.Empty) { continue; } + if (wordToComplete.Equals(value, StringComparison.OrdinalIgnoreCase)) { string completionText = quote == string.Empty ? value : quote + value + quote; diff --git a/src/System.Management.Automation/engine/CommandProcessor.cs b/src/System.Management.Automation/engine/CommandProcessor.cs index 72f7e10c9e..fdfeee9764 100644 --- a/src/System.Management.Automation/engine/CommandProcessor.cs +++ b/src/System.Management.Automation/engine/CommandProcessor.cs @@ -18,7 +18,7 @@ namespace System.Management.Automation /// internal class CommandProcessor : CommandProcessorBase { -#region ctor + #region ctor static CommandProcessor() { @@ -87,9 +87,10 @@ namespace System.Management.Automation // CommandProcessor -#endregion ctor + #endregion ctor + + #region internal members -#region internal members /// /// Returns a CmdletParameterBinderController for the specified command /// @@ -393,9 +394,9 @@ namespace System.Management.Automation } } -#endregion public_methods + #endregion public_methods -#region helper_methods + #region helper_methods /// /// Tells whether it is the first call to Read @@ -849,7 +850,7 @@ namespace System.Management.Automation return base.IsHelpRequested(out helpTarget, out helpCategory); } -#endregion helper_methods + #endregion helper_methods } } diff --git a/src/System.Management.Automation/engine/CommandProcessorBase.cs b/src/System.Management.Automation/engine/CommandProcessorBase.cs index 562f6a9572..475718f619 100644 --- a/src/System.Management.Automation/engine/CommandProcessorBase.cs +++ b/src/System.Management.Automation/engine/CommandProcessorBase.cs @@ -34,14 +34,31 @@ namespace System.Management.Automation /// The metadata about the command to run. /// /// - internal CommandProcessorBase( - CommandInfo commandInfo) + internal CommandProcessorBase(CommandInfo commandInfo) { if (commandInfo == null) { throw PSTraceSource.NewArgumentNullException("commandInfo"); } + if (commandInfo is IScriptCommandInfo scriptCommand) + { + var expAttribute = scriptCommand.ScriptBlock.ExperimentalAttribute; + if (expAttribute != null && expAttribute.ToHide) + { + string errorTemplate = expAttribute.ExperimentAction == ExperimentAction.Hide + ? DiscoveryExceptions.ScriptDisabledWhenFeatureOn + : DiscoveryExceptions.ScriptDisabledWhenFeatureOff; + string errorMsg = StringUtil.Format(errorTemplate, expAttribute.ExperimentName); + ErrorRecord errorRecord = new ErrorRecord( + new InvalidOperationException(errorMsg), + "ScriptCommandDisabled", + ErrorCategory.InvalidOperation, + commandInfo); + throw new CmdletInvocationException(errorRecord); + } + } + CommandInfo = commandInfo; } diff --git a/src/System.Management.Automation/engine/CompiledCommandParameter.cs b/src/System.Management.Automation/engine/CompiledCommandParameter.cs index 97422f46d4..070abdf42f 100644 --- a/src/System.Management.Automation/engine/CompiledCommandParameter.cs +++ b/src/System.Management.Automation/engine/CompiledCommandParameter.cs @@ -64,6 +64,18 @@ namespace System.Management.Automation // First, process attributes that aren't type conversions foreach (Attribute attribute in runtimeDefinedParameter.Attributes) { + if (processingDynamicParameters) + { + // When processing dynamic parameters, the attribute list may contain experimental attributes + // and disabled parameter attributes. We should ignore those attributes. + // When processing non-dynamic parameters, the experimental attributes and disabled parameter + // attributes have already been filtered out when constructing the RuntimeDefinedParameter. + if (attribute is ExperimentalAttribute || attribute is ParameterAttribute param && param.ToHide) + { + continue; + } + } + if (!(attribute is ArgumentTypeConverterAttribute)) { ProcessAttribute(runtimeDefinedParameter.Name, attribute, ref validationAttributes, ref argTransformationAttributes, ref aliases); @@ -78,7 +90,7 @@ namespace System.Management.Automation } // Now process type converters - foreach (ArgumentTypeConverterAttribute attribute in runtimeDefinedParameter.Attributes.OfType()) + foreach (var attribute in runtimeDefinedParameter.Attributes.OfType()) { ProcessAttribute(runtimeDefinedParameter.Name, attribute, ref validationAttributes, ref argTransformationAttributes, ref aliases); } @@ -169,7 +181,15 @@ namespace System.Management.Automation foreach (Attribute attr in memberAttributes) { - ProcessAttribute(member.Name, attr, ref validationAttributes, ref argTransformationAttributes, ref aliases); + switch (attr) + { + case ExperimentalAttribute _: + case ParameterAttribute param when param.ToHide: + break; + default: + ProcessAttribute(member.Name, attr, ref validationAttributes, ref argTransformationAttributes, ref aliases); + break; + } } this.ValidationAttributes = validationAttributes == null @@ -436,7 +456,6 @@ namespace System.Management.Automation ref Collection argTransformationAttributes, ref string[] aliases) { - // NTRAID#Windows Out Of Band Releases-926374-2005/12/22-JonN if (attribute == null) return; diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs new file mode 100644 index 0000000000..3c6b2060fb --- /dev/null +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -0,0 +1,309 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Immutable; +using System.Linq; +using System.Management.Automation.Configuration; +using System.Management.Automation.Internal; +using System.Runtime.CompilerServices; + +namespace System.Management.Automation +{ + /// + /// Support experimental features in PowerShell + /// + public class ExperimentalFeature + { + #region Const Members + + internal const string EngineSource = "PSEngine"; + + #endregion + + #region Instance Members + + /// + /// Name of an experimental feature. + /// + public string Name { get; } + + /// + /// Description of an experimental feature. + /// + public string Description { get; } + + /// + /// Source of an experimental feature. + /// + public string Source { get; } + + /// + /// Indicate whether the feature is enabled. + /// + public bool IsEnabled { get; private set; } + + /// + /// Constructor for ExperimentalFeature. + /// + internal ExperimentalFeature(string name, string description, string source, bool isEnabled) + { + Name = name; + Description = description; + Source = source; + IsEnabled = isEnabled; + } + + #endregion + + #region Static Members + + /// + /// All available engine experimental features. + /// + internal static readonly ReadOnlyCollection EngineExperimentalFeatures; + + /// + /// A dictionary of all available engine experimental features. Feature name is the key. + /// + internal static readonly ReadOnlyDictionary EngineExperimentalFeatureMap; + + /// + /// Experimental feature names that are enabled in the config file. + /// + internal static readonly ImmutableHashSet EnabledExperimentalFeatureNames; + + /// + /// Type initializer. Initialize the engine experimental feature list. + /// + static ExperimentalFeature() + { + // Initialize the readonly collection 'EngineExperimentalFeatures'. + var engineFeatures = new ExperimentalFeature[] { + /* Register engine experimental features here. Follow the same pattern as the example: + new ExperimentalFeature(name: "PSFileSystemProviderV2", + description: "Replace the old FileSystemProvider with cleaner design and faster code", + source: EngineSource, + isEnabled: false)) + */ + }; + EngineExperimentalFeatures = new ReadOnlyCollection(engineFeatures); + + // Initialize the readonly dictionary 'EngineExperimentalFeatureMap'. + var engineExpFeatureMap = engineFeatures.ToDictionary(f => f.Name, StringComparer.OrdinalIgnoreCase); + EngineExperimentalFeatureMap = new ReadOnlyDictionary(engineExpFeatureMap); + + // Initialize the immutable hashset 'EnabledExperimentalFeatureNames'. + // The initialization of 'EnabledExperimentalFeatureNames' is deliberately made in the type initializer so that: + // 1. 'EnabledExperimentalFeatureNames' can be declared as readonly; + // 2. No need to deal with initialization from multiple threads; + // 3. We don't need to decide where/when to read the config file for the enabled experimental features, + // instead, it will be done when the type is used for the first time, which is always earlier than + // any experimental features take effect. + string[] enabledFeatures = Utils.EmptyArray(); + try { enabledFeatures = PowerShellConfig.Instance.GetExperimentalFeatures(); } + catch (Exception ex) when (LogException(ex, "Placeholder")) { } + + EnabledExperimentalFeatureNames = ProcessEnabledFeatures(enabledFeatures); + } + + /// + /// Check if the specified experimental feature has been enabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasEnabled(string featureName) + { + return EnabledExperimentalFeatureNames.Contains(featureName); + } + + /// + /// Determine the action to take for the specified experiment name and action. + /// + internal static ExperimentAction GetActionToTake(string experimentName, ExperimentAction experimentAction) + { + if (experimentName == null || experimentAction == ExperimentAction.None) + { + // If either the experiment name or action is not defined, then return 'Show' by default. + // This could happen to 'ParameterAttribute' when no experimental related field is declared. + return ExperimentAction.Show; + } + + ExperimentAction action = experimentAction; + if (!HasEnabled(experimentName)) + { + action = (action == ExperimentAction.Hide) ? ExperimentAction.Show : ExperimentAction.Hide; + } + return action; + } + + /// + /// Check if the name follows the engine experimental feature name convention. + /// Convention: prefix 'PS' to the feature name -- PSFeatureName + /// + internal static bool IsEngineFeatureName(string featureName) + { + return featureName.IndexOf('.') == -1 && featureName.Length > 2 && featureName.StartsWith("PS", StringComparison.Ordinal); + } + + /// + /// Check if the name follows the module experimental feature name convention. + /// Convention: ModuleName.FeatureName + /// + /// The feature name to check. + /// When specified, we check if the feature name matches the module name + internal static bool IsModuleFeatureName(string featureName, string moduleName = null) + { + int firstDotIndex = featureName.IndexOf('.'); + int lastDotIndex = featureName.LastIndexOf('.'); + + bool legit = firstDotIndex > 0 && lastDotIndex < featureName.Length - 1; + if (legit && moduleName != null) + { + var moduleNamePart = featureName.AsSpan(0, lastDotIndex); + return moduleNamePart.Equals(moduleName.AsSpan(), StringComparison.OrdinalIgnoreCase); + } + return legit; + } + + /// + /// Log the exception without rewinding the stack. + /// + private static bool LogException(Exception ex, string errorTemplate) + { + // TODO: Logging + return false; + } + + /// + /// Log an error message. + /// + private static void LogError(string message) + { + // TODO: Logging + } + + /// + /// Process the array of enabled feature names retrieved from configuration. + /// Ignore invalid feature names and unavailable engine feature names, and + /// return an ImmutableHashSet of the valid enabled feature names. + /// + private static ImmutableHashSet ProcessEnabledFeatures(string[] enabledFeatures) + { + if (enabledFeatures.Length == 0) { return ImmutableHashSet.Create(); } + + var list = new List(enabledFeatures.Length); + foreach (string name in enabledFeatures) + { + if (IsModuleFeatureName(name)) + { + list.Add(name); + } + else if (IsEngineFeatureName(name)) + { + if (EngineExperimentalFeatureMap.TryGetValue(name, out ExperimentalFeature feature)) + { + feature.IsEnabled = true; + list.Add(name); + } + else + { + LogError("No such engine feature registered. Ignore it."); + } + } + else + { + LogError("Invalid experimental feature name. Ignore it"); + } + } + return ImmutableHashSet.CreateRange(StringComparer.OrdinalIgnoreCase, list); + } + + #endregion + } + + /// + /// Indicates the action to take on the cmdlet/parameter that has the attribute declared. + /// + public enum ExperimentAction + { + /// + /// Represent an undefined action, used as the default value. + /// + None = 0, + + /// + /// Hide the cmdlet/parameter when the corresponding experimental feature is enabled. + /// + Hide = 1, + + /// + /// Show the cmdlet/parameter when the corresponding experimental feature is enabled. + /// + Show = 2 + } + + /// + /// The attribute that applies to cmdlet/function/parameter to define what the engine should do with it. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class ExperimentalAttribute : ParsingBaseAttribute + { + /// + /// Specify the experimental feature this attribute is associated with. + /// + public string ExperimentName { get; } + + /// + /// Specify the action for engine to take when the experimental feature is enabled. + /// + public ExperimentAction ExperimentAction { get; } + + /// + /// Initializes a new instance of the ExperimentalAttribute class. + /// + public ExperimentalAttribute(string experimentName, ExperimentAction experimentAction) + { + if (string.IsNullOrEmpty(experimentName)) + { + throw PSTraceSource.NewArgumentException(nameof(experimentName)); + } + + if (experimentAction == ExperimentAction.None) + { + throw PSTraceSource.NewArgumentException(nameof(experimentAction)); + } + + ExperimentName = experimentName; + ExperimentAction = experimentAction; + } + + /// + /// Initialize an instance that represents the none-value. + /// + private ExperimentalAttribute() {} + + /// + /// An instance that represents the none-value. + /// + internal readonly static ExperimentalAttribute None = new ExperimentalAttribute(); + + internal bool ToHide => EffectiveAction == ExperimentAction.Hide; + internal bool ToShow => EffectiveAction == ExperimentAction.Show; + + /// + /// Effective action to take at run time. + /// + private ExperimentAction EffectiveAction + { + get { + if (_effectiveAction == ExperimentAction.None) + { + _effectiveAction = ExperimentalFeature.GetActionToTake(ExperimentName, ExperimentAction); + } + return _effectiveAction; + } + } + private ExperimentAction _effectiveAction = default(ExperimentAction); + } +} \ No newline at end of file diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 320961e167..c31eba64b5 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -13,12 +13,13 @@ using System.Management.Automation.Internal; using System.Management.Automation.Provider; using System.Management.Automation.Language; using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading; using Microsoft.PowerShell.Commands; -using Debug = System.Management.Automation.Diagnostics; using System.Management.Automation.Host; using System.Text; using System.Threading.Tasks; +using Debug = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Runspaces { @@ -3752,7 +3753,7 @@ namespace System.Management.Automation.Runspaces { s_PSSnapInTracer.WriteLine("Loading assembly for psSnapIn {0}", psSnapInInfo.Name); - assembly = PSSnapInHelpers.LoadPSSnapInAssembly(psSnapInInfo, out cmdlets, out providers); + assembly = PSSnapInHelpers.LoadPSSnapInAssembly(psSnapInInfo); if (assembly == null) { @@ -4897,13 +4898,9 @@ end internal static class PSSnapInHelpers { [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")] - internal static Assembly LoadPSSnapInAssembly(PSSnapInInfo psSnapInInfo, - out Dictionary cmdlets, out Dictionary providers) + internal static Assembly LoadPSSnapInAssembly(PSSnapInInfo psSnapInInfo) { Assembly assembly = null; - cmdlets = null; - providers = null; - s_PSSnapInTracer.WriteLine("Loading assembly from GAC. Assembly Name: {0}", psSnapInInfo.AssemblyName); try @@ -4960,13 +4957,11 @@ end return assembly; } - private static T GetCustomAttribute(Type decoratedType) where T : Attribute + private static bool GetCustomAttribute(Type decoratedType, out T attribute) where T : Attribute { - var attributes = decoratedType.GetCustomAttributes(false); - var customAttrs = attributes.ToArray(); - - Debug.Assert(customAttrs.Length <= 1, "CmdletAttribute and/or CmdletProviderAttribute cannot normally appear more than once"); - return customAttrs.Length == 0 ? null : customAttrs[0]; + var attributes = decoratedType.GetCustomAttributes(inherit: false); + attribute = attributes.FirstOrDefault(); + return attribute != null; } internal static void AnalyzePSSnapInAssembly(Assembly assembly, string name, PSSnapInInfo psSnapInInfo, PSModuleInfo moduleInfo, bool isModuleLoad, @@ -4976,7 +4971,7 @@ end helpFile = null; if (assembly == null) { - throw new ArgumentNullException("assembly"); + throw new ArgumentNullException(nameof(assembly)); } cmdlets = null; @@ -4988,8 +4983,8 @@ end Dictionary>> cachedCmdlets; if (s_cmdletCache.Value.TryGetValue(assembly, out cachedCmdlets)) { - cmdlets = new Dictionary(s_cmdletCache.Value.Count, StringComparer.OrdinalIgnoreCase); - aliases = new Dictionary>(StringComparer.OrdinalIgnoreCase); + cmdlets = new Dictionary(cachedCmdlets.Count, StringComparer.OrdinalIgnoreCase); + aliases = new Dictionary>(cachedCmdlets.Count, StringComparer.OrdinalIgnoreCase); foreach (var pair in cachedCmdlets) { @@ -5051,30 +5046,15 @@ end } string assemblyPath = assembly.Location; - Type[] assemblyTypes; if (cmdlets != null || providers != null) { - if (!s_assembliesWithModuleInitializerCache.Value.ContainsKey(assembly)) - { - s_PSSnapInTracer.WriteLine("Returning cached cmdlet and provider entries for {0}", assemblyPath); - return; - } - else - { - s_PSSnapInTracer.WriteLine("Executing IModuleAssemblyInitializer.Import for {0}", assemblyPath); - assemblyTypes = GetAssemblyTypes(assembly, name); - ExecuteModuleInitializer(assembly, assemblyTypes, isModuleLoad); - return; - } + s_PSSnapInTracer.WriteLine("Returning cached cmdlet and provider entries for {0}", assemblyPath); + return; } s_PSSnapInTracer.WriteLine("Analyzing assembly {0} for cmdlet and providers", assemblyPath); - helpFile = GetHelpFile(assemblyPath); - Type randomCmdletToCheckLinkDemand = null; - Type randomProviderToCheckLinkDemand = null; - if (psSnapInInfo != null && psSnapInInfo.Name.Equals(InitialSessionState.CoreSnapin, StringComparison.OrdinalIgnoreCase)) { InitializeCoreCmdletsAndProviders(psSnapInInfo, out cmdlets, out providers, helpFile); @@ -5086,13 +5066,10 @@ end Dictionary cmdletsCheck = null; Dictionary providersCheck = null; Dictionary> aliasesCheck = null; - Type unused1 = null; - Type unused2 = null; - AnalyzeModuleAssemblyWithReflection(assembly, name, psSnapInInfo, moduleInfo, isModuleLoad, - ref cmdletsCheck, ref aliasesCheck, ref providersCheck, helpFile, ref unused1, ref unused2); + AnalyzeModuleAssemblyWithReflection(assembly, name, psSnapInInfo, moduleInfo, isModuleLoad, ref cmdletsCheck, ref aliasesCheck, ref providersCheck, helpFile); Diagnostics.Assert(aliasesCheck == null, "InitializeCoreCmdletsAndProviders assumes no aliases are defined in System.Management.Automation.dll"); - Diagnostics.Assert(providersCheck.Keys.Count == providers.Keys.Count, "new Provider added to System.Management.Automation.dll - update InitializeCoreCmdletsAndProviders"); + Diagnostics.Assert(providersCheck.Count == providers.Count, "new Provider added to System.Management.Automation.dll - update InitializeCoreCmdletsAndProviders"); foreach (var pair in providersCheck) { SessionStateProviderEntry other; @@ -5110,7 +5087,7 @@ end Diagnostics.Assert(false, "Missing provider: " + pair.Key); } } - Diagnostics.Assert(cmdletsCheck.Keys.Count == cmdlets.Keys.Count, "new Cmdlet added to System.Management.Automation.dll - update InitializeCoreCmdletsAndProviders"); + Diagnostics.Assert(cmdletsCheck.Count == cmdlets.Count, "new Cmdlet added to System.Management.Automation.dll - update InitializeCoreCmdletsAndProviders"); foreach (var pair in cmdletsCheck) { @@ -5133,39 +5110,7 @@ end } else { - AnalyzeModuleAssemblyWithReflection(assembly, name, psSnapInInfo, moduleInfo, isModuleLoad, - ref cmdlets, ref aliases, ref providers, helpFile, ref randomCmdletToCheckLinkDemand, ref randomProviderToCheckLinkDemand); - } - - // force a LinkDemand check to get an explicit exception if - // Cmdlet[Provider]Attributes are silently swallowed by Type.GetCustomAttributes - // bug Win7:705573 - if ((providers == null || providers.Count == 0) && (cmdlets == null || cmdlets.Count == 0)) - { - try - { - if (randomCmdletToCheckLinkDemand != null) - { - ConstructorInfo constructor = randomCmdletToCheckLinkDemand.GetConstructor(PSTypeExtensions.EmptyTypes); - if (constructor != null) - { - constructor.Invoke(null); // this is how we artificially force a LinkDemand check - } - } - - if (randomProviderToCheckLinkDemand != null) - { - ConstructorInfo constructor = randomProviderToCheckLinkDemand.GetConstructor(PSTypeExtensions.EmptyTypes); - if (constructor != null) - { - constructor.Invoke(null); // this is how we artificially force a LinkDemand check - } - } - } - catch (TargetInvocationException e) - { - throw e.InnerException; - } + AnalyzeModuleAssemblyWithReflection(assembly, name, psSnapInInfo, moduleInfo, isModuleLoad, ref cmdlets, ref aliases, ref providers, helpFile); } // Cache the cmdlet and provider info for this assembly... @@ -5212,118 +5157,83 @@ end ref Dictionary cmdlets, ref Dictionary> aliases, ref Dictionary providers, - string helpFile, - ref Type randomCmdletToCheckLinkDemand, - ref Type randomProviderToCheckLinkDemand) + string helpFile) { var assemblyTypes = GetAssemblyTypes(assembly, name); - ExecuteModuleInitializer(assembly, assemblyTypes, isModuleLoad); foreach (Type type in assemblyTypes) { - if (!(type.IsPublic || type.IsNestedPublic) || type.IsAbstract) - continue; + if (!HasDefaultConstructor(type)) { continue; } // Check for cmdlets - if (IsCmdletClass(type) && HasDefaultConstructor(type)) + if (IsCmdletClass(type) && GetCustomAttribute(type, out CmdletAttribute cmdletAttribute)) { - randomCmdletToCheckLinkDemand = type; - - CmdletAttribute cmdletAttribute = GetCustomAttribute(type); - if (cmdletAttribute == null) - { - continue; - } - string cmdletName = GetCmdletName(cmdletAttribute); - if (string.IsNullOrEmpty(cmdletName)) + if (GetCustomAttribute(type, out ExperimentalAttribute expAttribute) && expAttribute.ToHide) { + // If 'ExperimentalAttribute' is specified on the cmdlet type and the + // effective action at run time is 'Hide', then we ignore the type. continue; } + string cmdletName = cmdletAttribute.VerbName + "-" + cmdletAttribute.NounName; if (cmdlets != null && cmdlets.ContainsKey(cmdletName)) { string message = StringUtil.Format(ConsoleInfoErrorStrings.PSSnapInDuplicateCmdlets, cmdletName, name); - s_PSSnapInTracer.TraceError(message); - throw new PSSnapInException(name, message); } SessionStateCmdletEntry cmdlet = new SessionStateCmdletEntry(cmdletName, type, helpFile); - cmdlet.SetPSSnapIn(psSnapInInfo); - if (cmdlets == null) - { - cmdlets = new Dictionary(StringComparer.OrdinalIgnoreCase); - } + if (psSnapInInfo != null) { cmdlet.SetPSSnapIn(psSnapInInfo); } + if (moduleInfo != null) { cmdlet.SetModule(moduleInfo); } + + cmdlets = cmdlets ?? new Dictionary(StringComparer.OrdinalIgnoreCase); cmdlets.Add(cmdletName, cmdlet); - var aliasAttribute = GetCustomAttribute(type); - if (aliasAttribute != null) + if (GetCustomAttribute(type, out AliasAttribute aliasAttribute)) { - if (aliases == null) - { - aliases = new Dictionary>(StringComparer.OrdinalIgnoreCase); - } + aliases = aliases ?? new Dictionary>(StringComparer.OrdinalIgnoreCase); var aliasList = new List(); foreach (var alias in aliasAttribute.AliasNames) { - // Alias declared by AliasAttribute is set with the option 'ScopedItemOptions.None', - // because we believe a user of the cmdlet, instead of the author of it, - // should be the one to decide the option - // ('ScopedItemOptions.ReadOnly' and/or 'ScopedItemOptions.AllScopes') of the alias usage." - var aliasEntry = new SessionStateAliasEntry(alias, cmdletName, string.Empty, ScopedItemOptions.None); - if (psSnapInInfo != null) - { - aliasEntry.SetPSSnapIn(psSnapInInfo); - } + // Alias declared by 'AliasAttribute' is set with the option 'ScopedItemOptions.None', because we believe + // the users of the cmdlet, instead of the author, should have control of what options applied to an alias + // ('ScopedItemOptions.ReadOnly' and/or 'ScopedItemOptions.AllScopes'). + var aliasEntry = new SessionStateAliasEntry(alias, cmdletName, description: string.Empty, ScopedItemOptions.None); + if (psSnapInInfo != null) { aliasEntry.SetPSSnapIn(psSnapInInfo); } + if (moduleInfo != null) { aliasEntry.SetModule(moduleInfo); } aliasList.Add(aliasEntry); } aliases.Add(cmdletName, aliasList); } s_PSSnapInTracer.WriteLine("{0} from type {1} is added as a cmdlet. ", cmdletName, type.FullName); - continue; } - // Check for providers - if (IsProviderClass(type) && HasDefaultConstructor(type)) + else if (IsProviderClass(type) && GetCustomAttribute(type, out CmdletProviderAttribute providerAttribute)) { - randomProviderToCheckLinkDemand = type; - - CmdletProviderAttribute providerAttribute = GetCustomAttribute(type); - if (providerAttribute == null) - { - continue; - } - string providerName = GetProviderName(providerAttribute); - if (string.IsNullOrEmpty(providerName)) + if (GetCustomAttribute(type, out ExperimentalAttribute expAttribute) && expAttribute.ToHide) { + // If 'ExperimentalAttribute' is specified on the provider type and + // the effective action at run time is 'Hide', then we ignore the type. continue; } + string providerName = providerAttribute.ProviderName; if (providers != null && providers.ContainsKey(providerName)) { string message = StringUtil.Format(ConsoleInfoErrorStrings.PSSnapInDuplicateProviders, providerName, psSnapInInfo.Name); - s_PSSnapInTracer.TraceError(message); - throw new PSSnapInException(psSnapInInfo.Name, message); } SessionStateProviderEntry provider = new SessionStateProviderEntry(providerName, type, helpFile); - provider.SetPSSnapIn(psSnapInInfo); + if (psSnapInInfo != null) { provider.SetPSSnapIn(psSnapInInfo); } + if (moduleInfo != null) { provider.SetModule(moduleInfo); } - // After converting core snapins to load as modules, the providers will have Module property populated - if (moduleInfo != null) - { - provider.SetModule(moduleInfo); - } - if (providers == null) - { - providers = new Dictionary(StringComparer.OrdinalIgnoreCase); - } + providers = providers ?? new Dictionary(StringComparer.OrdinalIgnoreCase); providers.Add(providerName, provider); s_PSSnapInTracer.WriteLine("{0} from type {1} is added as a provider. ", providerName, type.FullName); @@ -5431,39 +5341,28 @@ end } } - private static void ExecuteModuleInitializer(Assembly assembly, Type[] assemblyTypes, bool isModuleLoad) + private static void ExecuteModuleInitializer(Assembly assembly, IEnumerable assemblyTypes, bool isModuleLoad) { - for (int i = 0; i < assemblyTypes.Length; i++) + foreach (Type type in assemblyTypes) { - Type type = assemblyTypes[i]; - if (!(type.IsPublic || type.IsNestedPublic) || type.IsAbstract) { continue; } - - if (isModuleLoad && typeof(IModuleAssemblyInitializer).IsAssignableFrom(type) && type != typeof(IModuleAssemblyInitializer)) + if (isModuleLoad && typeof(IModuleAssemblyInitializer).IsAssignableFrom(type)) { - s_assembliesWithModuleInitializerCache.Value[assembly] = true; IModuleAssemblyInitializer moduleInitializer = (IModuleAssemblyInitializer)Activator.CreateInstance(type, true); moduleInitializer.OnImport(); } } } - internal static Type[] GetAssemblyTypes(Assembly assembly, string name) + internal static IEnumerable GetAssemblyTypes(Assembly assembly, string name) { - Type[] assemblyTypes = null; - try { - var exportedTypes = assembly.ExportedTypes; - assemblyTypes = exportedTypes as Type[] ?? exportedTypes.ToArray(); + // Return types that are public, non-abstract, non-interface and non-valueType. + return assembly.ExportedTypes.Where(t => !t.IsAbstract && !t.IsInterface && !t.IsValueType); } catch (ReflectionTypeLoadException e) { - string message; - - message = e.Message; - - message += "\nLoader Exceptions: \n"; - + string message = e.Message + "\nLoader Exceptions: \n"; if (e.LoaderExceptions != null) { foreach (Exception exception in e.LoaderExceptions) @@ -5473,10 +5372,8 @@ end } s_PSSnapInTracer.TraceError(message); - throw new PSSnapInException(name, message); } - return assemblyTypes; } // cmdletCache holds the list of cmdlets along with its aliases per each assembly. @@ -5484,54 +5381,26 @@ end new Lazy>>>>(); private static Lazy>> s_providerCache = new Lazy>>(); - // Using a ConcurrentDictionary for this so that we can avoid having a private lock variable. We use only the keys for checking. - private static Lazy> s_assembliesWithModuleInitializerCache = new Lazy>(); - - private static string GetCmdletName(CmdletAttribute cmdletAttribute) - { - string verb = cmdletAttribute.VerbName; - - string noun = cmdletAttribute.NounName; - - return verb + "-" + noun; - } - - private static string GetProviderName(CmdletProviderAttribute providerAttribute) - { - return providerAttribute.ProviderName; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsCmdletClass(Type type) { - if (type == null) - return false; - return type.IsSubclassOf(typeof(System.Management.Automation.Cmdlet)); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsProviderClass(Type type) { - if (type == null) - return false; - return type.IsSubclassOf(typeof(System.Management.Automation.Provider.CmdletProvider)); } - internal static bool IsModuleAssemblyInitializerClass(Type type) - { - if (type == null) - { - return false; - } - - return type.IsSubclassOf(typeof(System.Management.Automation.IModuleAssemblyInitializer)); - } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool HasDefaultConstructor(Type type) { return !(type.GetConstructor(PSTypeExtensions.EmptyTypes) == null); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string GetHelpFile(string assemblyPath) { // Help files exist only for original module assemblies, not for generated Ngen binaries diff --git a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs index 9d019f95b0..11107ceac1 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs @@ -4672,7 +4672,7 @@ namespace Microsoft.PowerShell.Commands var exportedTypes = PSSnapInHelpers.GetAssemblyTypes(module.ImplementingAssembly, module.Name); foreach (var type in exportedTypes) { - if (typeof(IModuleAssemblyCleanup).IsAssignableFrom(type) && type != typeof(IModuleAssemblyCleanup)) + if (typeof(IModuleAssemblyCleanup).IsAssignableFrom(type)) { var moduleCleanup = (IModuleAssemblyCleanup)Activator.CreateInstance(type, true); moduleCleanup.OnRemove(module); diff --git a/src/System.Management.Automation/engine/PSConfiguration.cs b/src/System.Management.Automation/engine/PSConfiguration.cs index 18d7814a2f..7f3597f840 100644 --- a/src/System.Management.Automation/engine/PSConfiguration.cs +++ b/src/System.Management.Automation/engine/PSConfiguration.cs @@ -193,6 +193,14 @@ namespace System.Management.Automation.Configuration WriteValueToFile(ConfigScope.SystemWide, "DisablePromptToUpdateHelp", prompt); } + /// + /// Get the names of experimental features enabled in the config file. + /// + internal string[] GetExperimentalFeatures() + { + return ReadValueFromFile(ConfigScope.SystemWide, "ExperimentalFeatures", Utils.EmptyArray()); + } + /// /// Corresponding settings of the original Group Policies /// diff --git a/src/System.Management.Automation/engine/PseudoParameters.cs b/src/System.Management.Automation/engine/PseudoParameters.cs index 50b8ff4ef2..e73ea5fb79 100644 --- a/src/System.Management.Automation/engine/PseudoParameters.cs +++ b/src/System.Management.Automation/engine/PseudoParameters.cs @@ -164,7 +164,36 @@ namespace System.Management.Automation /// This can be any attribute that can be applied to a normal parameter. /// public Collection Attributes { get; } = new Collection(); - } // class RuntimeDefinedParameter + + /// + /// Check if the parameter is disabled due to the associated experimental feature. + /// + internal bool IsDisabled() + { + bool hasParameterAttribute = false; + bool hasEnabledParamAttribute = false; + bool hasSeenExpAttribute = false; + + foreach (var attr in Attributes) + { + if (!hasSeenExpAttribute && attr is ExperimentalAttribute expAttribute) + { + if (expAttribute.ToHide) { return true; } + hasSeenExpAttribute = true; + } + else if (attr is ParameterAttribute paramAttribute) + { + hasParameterAttribute = true; + if (paramAttribute.ToHide) { continue; } + hasEnabledParamAttribute = true; + } + } + + // If one or more parameter attributes are declared but none is enabled, + // then we consider the parameter is disabled. + return hasParameterAttribute && !hasEnabledParamAttribute; + } + } /// /// Represents a collection of runtime-defined parameters that are keyed based on the name diff --git a/src/System.Management.Automation/engine/ScriptCommandProcessor.cs b/src/System.Management.Automation/engine/ScriptCommandProcessor.cs index e14f0616f7..2ba86c6725 100644 --- a/src/System.Management.Automation/engine/ScriptCommandProcessor.cs +++ b/src/System.Management.Automation/engine/ScriptCommandProcessor.cs @@ -17,10 +17,9 @@ namespace System.Management.Automation internal abstract class ScriptCommandProcessorBase : CommandProcessorBase { protected ScriptCommandProcessorBase(ScriptBlock scriptBlock, ExecutionContext context, bool useLocalScope, CommandOrigin origin, SessionStateInternal sessionState) + : base(new ScriptInfo(String.Empty, scriptBlock, context)) { this._dontUseScopeCommandOrigin = false; - this.CommandInfo = new ScriptInfo(String.Empty, scriptBlock, context); - this._fromScriptFile = false; CommonInitialization(scriptBlock, context, useLocalScope, origin, sessionState); diff --git a/src/System.Management.Automation/engine/TypeMetadata.cs b/src/System.Management.Automation/engine/TypeMetadata.cs index 4cf45a6dd0..074a306a5f 100644 --- a/src/System.Management.Automation/engine/TypeMetadata.cs +++ b/src/System.Management.Automation/engine/TypeMetadata.cs @@ -1308,10 +1308,12 @@ namespace System.Management.Automation foreach (RuntimeDefinedParameter parameterDefinition in runtimeDefinedParameters.Values) { // Create the compiled parameter and add it to the bindable parameters collection - - // NTRAID#Windows Out Of Band Releases-926374-2005/12/22-JonN - if (parameterDefinition == null) - continue; + if (processingDynamicParameters) + { + // When processing dynamic parameters, parameter definitions come from the user, + // Invalid data could be passed in, or the parameter could be actually disabled. + if (parameterDefinition == null || parameterDefinition.IsDisabled()) { continue; } + } CompiledCommandParameter parameter = new CompiledCommandParameter(parameterDefinition, processingDynamicParameters); AddParameter(parameter, checkNames); @@ -1379,7 +1381,6 @@ namespace System.Management.Automation } } - // NTRAID#Windows Out Of Band Releases-906345-2005/06/30-JeffJon // This call verifies that the parameter is unique or // can be deemed unique. If not, an exception is thrown. // If it is unique (or deemed unique), then it is added @@ -1458,7 +1459,6 @@ namespace System.Management.Automation foreach (string alias in parameter.Aliases) { - // NTRAID#Windows Out Of Band Releases-917356-JonN if (AliasedParameters.ContainsKey(alias)) { throw new MetadataException( @@ -1501,16 +1501,20 @@ namespace System.Management.Automation /// private static bool IsMemberAParameter(MemberInfo member) { - bool result = false; - try { - // MemberInfo.GetCustomAttributes returns IEnumerable in CoreCLR - var attributes = member.GetCustomAttributes(typeof(ParameterAttribute), false); - if (attributes.Any()) + var expAttribute = member.GetCustomAttributes(false).FirstOrDefault(); + if (expAttribute != null && expAttribute.ToHide) { return false; } + + var hasAnyVisibleParamAttributes = false; + var paramAttributes = member.GetCustomAttributes(false); + foreach (var paramAttribute in paramAttributes) { - result = true; + if (paramAttribute.ToHide) { continue; } + hasAnyVisibleParamAttributes = true; + break; } + return hasAnyVisibleParamAttributes; } catch (MetadataException metadataException) { @@ -1530,8 +1534,6 @@ namespace System.Management.Automation member.Name, argumentException.Message); } - - return result; } // IsMemberAParameter #endregion helper methods diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 3426d326a9..ee910e59ea 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -1001,15 +1001,17 @@ namespace System.Management.Automation.Language internal static RuntimeDefinedParameterDictionary GetParameterMetaData(ReadOnlyCollection parameters, bool automaticPositions, ref bool usesCmdletBinding) { var md = new RuntimeDefinedParameterDictionary(); - var listMd = new List(); + var listMd = new List(parameters.Count); var customParameterSet = false; for (int index = 0; index < parameters.Count; index++) { var param = parameters[index]; var rdp = GetRuntimeDefinedParameter(param, ref customParameterSet, ref usesCmdletBinding); - - listMd.Add(rdp); - md.Add(param.Name.VariablePath.UserPath, rdp); + if (rdp != null) + { + listMd.Add(rdp); + md.Add(param.Name.VariablePath.UserPath, rdp); + } } int pos = 0; @@ -1447,28 +1449,47 @@ namespace System.Management.Automation.Language private static RuntimeDefinedParameter GetRuntimeDefinedParameter(ParameterAst parameterAst, ref bool customParameterSet, ref bool usesCmdletBinding) { - List attributes = new List(); + List attributes = new List(parameterAst.Attributes.Count); bool hasParameterAttribute = false; + bool hasEnabledParamAttribute = false; + bool hasSeenExpAttribute = false; + for (int index = 0; index < parameterAst.Attributes.Count; index++) { var attributeAst = parameterAst.Attributes[index]; var attribute = attributeAst.GetAttribute(); - attributes.Add(attribute); - var parameterAttribute = attribute as ParameterAttribute; - if (parameterAttribute != null) + if (attribute is ExperimentalAttribute expAttribute) + { + // Only honor the first seen experimental attribute, ignore the others. + if (!hasSeenExpAttribute && expAttribute.ToHide) { return null; } + + // Do not add experimental attributes to the attribute list. + hasSeenExpAttribute = true; + continue; + } + else if (attribute is ParameterAttribute paramAttribute) { hasParameterAttribute = true; + if (paramAttribute.ToHide) { continue; } + + hasEnabledParamAttribute = true; usesCmdletBinding = true; - if (parameterAttribute.Position != int.MinValue || - !parameterAttribute.ParameterSetName.Equals(ParameterAttribute.AllParameterSets, + if (paramAttribute.Position != int.MinValue || + !paramAttribute.ParameterSetName.Equals(ParameterAttribute.AllParameterSets, StringComparison.OrdinalIgnoreCase)) { customParameterSet = true; } } + + attributes.Add(attribute); } + // If all 'ParameterAttribute' declared for the parameter are hidden due to + // an experimental feature, then the parameter should be ignored. + if (hasParameterAttribute && !hasEnabledParamAttribute) { return null; } + attributes.Reverse(); if (!hasParameterAttribute) { @@ -1476,7 +1497,7 @@ namespace System.Management.Automation.Language } var result = new RuntimeDefinedParameter(parameterAst.Name.VariablePath.UserPath, parameterAst.StaticType, - new Collection(attributes.ToArray())); + new Collection(attributes)); if (parameterAst.DefaultValue != null) { diff --git a/src/System.Management.Automation/engine/parser/TypeResolver.cs b/src/System.Management.Automation/engine/parser/TypeResolver.cs index e63a53d2ee..d347457358 100644 --- a/src/System.Management.Automation/engine/parser/TypeResolver.cs +++ b/src/System.Management.Automation/engine/parser/TypeResolver.cs @@ -732,6 +732,7 @@ namespace System.Management.Automation { typeof(decimal), new[] { "decimal" } }, { typeof(double), new[] { "double" } }, { typeof(DscResourceAttribute), new[] { "DscResource"} }, + { typeof(ExperimentalAttribute), new[] { "Experimental" } }, { typeof(float), new[] { "float", "single" } }, { typeof(Guid), new[] { "guid" } }, { typeof(Hashtable), new[] { "hashtable" } }, diff --git a/src/System.Management.Automation/engine/parser/ast.cs b/src/System.Management.Automation/engine/parser/ast.cs index 46cf2d9da3..2a9757b54b 100644 --- a/src/System.Management.Automation/engine/parser/ast.cs +++ b/src/System.Management.Automation/engine/parser/ast.cs @@ -55,6 +55,7 @@ namespace System.Management.Automation.Language bool HasAnyScriptBlockAttributes(); RuntimeDefinedParameterDictionary GetParameterMetadata(bool automaticPositions, ref bool usesCmdletBinding); IEnumerable GetScriptBlockAttributes(); + IEnumerable GetExperimentalAttributes(); bool UsesCmdletBinding(); ReadOnlyCollection Parameters { get; } @@ -1370,6 +1371,61 @@ namespace System.Management.Automation.Language } } + IEnumerable IParameterMetadataProvider.GetExperimentalAttributes() + { + for (int index = 0; index < Attributes.Count; index++) + { + var attributeAst = Attributes[index]; + var expAttr = GetExpAttributeHelper(attributeAst); + if (expAttr != null) { yield return expAttr; } + } + + if (ParamBlock != null) + { + for (int index = 0; index < ParamBlock.Attributes.Count; index++) + { + var attributeAst = ParamBlock.Attributes[index]; + var expAttr = GetExpAttributeHelper(attributeAst); + if (expAttr != null) { yield return expAttr; } + } + } + + ExperimentalAttribute GetExpAttributeHelper(AttributeAst attributeAst) + { + AttributeAst potentialExpAttr = null; + string expAttrTypeName = typeof(ExperimentalAttribute).FullName; + string attrAstTypeName = attributeAst.TypeName.Name; + + if (TypeAccelerators.Get.TryGetValue(attrAstTypeName, out Type attrType) && attrType == typeof(ExperimentalAttribute)) + { + potentialExpAttr = attributeAst; + } + else if (expAttrTypeName.EndsWith(attrAstTypeName, StringComparison.OrdinalIgnoreCase)) + { + // Handle two cases: + // 1. declare the attribute using full type name; + // 2. declare the attribute using partial type name due to 'using namespace'. + int expAttrLength = expAttrTypeName.Length; + int attrAstLength = attrAstTypeName.Length; + if (expAttrLength == attrAstLength || expAttrTypeName[expAttrLength - attrAstLength - 1] == '.') + { + potentialExpAttr = attributeAst; + } + } + + if (potentialExpAttr != null) + { + try + { + return Compiler.GetAttribute(potentialExpAttr) as ExperimentalAttribute; + } + catch (Exception) { /* catch all and assume it's not a declaration of ExperimentalAttribute */ } + } + + return null; + } + } + ReadOnlyCollection IParameterMetadataProvider.Parameters { get { return (ParamBlock != null) ? this.ParamBlock.Parameters : null; } @@ -3299,6 +3355,11 @@ namespace System.Management.Automation.Language return ((IParameterMetadataProvider)_functionDefinitionAst).GetScriptBlockAttributes(); } + IEnumerable IParameterMetadataProvider.GetExperimentalAttributes() + { + return ((IParameterMetadataProvider)_functionDefinitionAst).GetExperimentalAttributes(); + } + bool IParameterMetadataProvider.UsesCmdletBinding() { return ((IParameterMetadataProvider)_functionDefinitionAst).UsesCmdletBinding(); @@ -3412,6 +3473,11 @@ namespace System.Management.Automation.Language return ((IParameterMetadataProvider)Body).GetScriptBlockAttributes(); } + public IEnumerable GetExperimentalAttributes() + { + return ((IParameterMetadataProvider)Body).GetExperimentalAttributes(); + } + public bool UsesCmdletBinding() { return false; @@ -3693,6 +3759,11 @@ namespace System.Management.Automation.Language return ((IParameterMetadataProvider)Body).GetScriptBlockAttributes(); } + IEnumerable IParameterMetadataProvider.GetExperimentalAttributes() + { + return ((IParameterMetadataProvider)Body).GetExperimentalAttributes(); + } + ReadOnlyCollection IParameterMetadataProvider.Parameters { get { return Parameters ?? (Body.ParamBlock != null ? Body.ParamBlock.Parameters : null); } diff --git a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs index 47e7c3ff2f..72e223a873 100644 --- a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs +++ b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs @@ -348,6 +348,26 @@ namespace System.Management.Automation } } + internal ExperimentalAttribute ExperimentalAttribute + { + get + { + if (_expAttribute == ExperimentalAttribute.None) + { + lock (this) + { + if (_expAttribute == ExperimentalAttribute.None) + { + _expAttribute = Ast.GetExperimentalAttributes().FirstOrDefault(); + } + } + } + + return _expAttribute; + } + } + private ExperimentalAttribute _expAttribute = ExperimentalAttribute.None; + public MergedCommandParameterMetadata GetParameterMetadata(ScriptBlock scriptBlock) { if (_parameterMetadata == null) @@ -1228,6 +1248,11 @@ namespace System.Management.Automation get { return _scriptBlockData.ObsoleteAttribute; } } + internal ExperimentalAttribute ExperimentalAttribute + { + get { return _scriptBlockData.ExperimentalAttribute; } + } + internal bool Compile(bool optimized) { return _scriptBlockData.Compile(optimized); diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index 74b0210e76..ddd148c72d 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -1212,8 +1212,12 @@ namespace System.Management.Automation ScriptBlock scriptBlock = scriptBlockExpressionWrapper.GetScriptBlock( context, functionDefinitionAst.IsFilter); - context.EngineSessionState.SetFunctionRaw(functionDefinitionAst.Name, - scriptBlock, context.EngineSessionState.CurrentScope.ScopeOrigin); + var expAttribute = scriptBlock.ExperimentalAttribute; + if (expAttribute == null || expAttribute.ToShow) + { + context.EngineSessionState.SetFunctionRaw(functionDefinitionAst.Name, + scriptBlock, context.EngineSessionState.CurrentScope.ScopeOrigin); + } } catch (Exception exception) { diff --git a/src/System.Management.Automation/resources/DiscoveryExceptions.resx b/src/System.Management.Automation/resources/DiscoveryExceptions.resx index 7e1a3bcf57..795a23a632 100644 --- a/src/System.Management.Automation/resources/DiscoveryExceptions.resx +++ b/src/System.Management.Automation/resources/DiscoveryExceptions.resx @@ -218,4 +218,10 @@ The #requires statement must be in one of the following formats: The ShowCommandInfo and Syntax parameters cannot be specified together. + + This script command is disabled when the experimental feature '{0}' has been turned on. + + + This script command is disabled when the experimental feature '{0}' has been turned off. +