mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
First batch: Basic experimental feature flag and attributes enforced
This commit is contained in:
@@ -620,12 +620,63 @@ namespace System.Management.Automation
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance that is associated with an experimental feature.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Name of the experimental feature this attribute is associated with.
|
||||
/// </summary>
|
||||
public string ExperimentName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Action for 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>
|
||||
/// 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 and sets the parameter position. If not set, the parameter is named.
|
||||
/// </summary>
|
||||
|
||||
@@ -1758,8 +1758,10 @@ namespace System.Management.Automation
|
||||
List<CompletionResult> result = new List<CompletionResult>();
|
||||
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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace System.Management.Automation
|
||||
/// </summary>
|
||||
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
|
||||
/// <summary>
|
||||
/// Returns a CmdletParameterBinderController for the specified command
|
||||
/// </summary>
|
||||
@@ -393,9 +394,9 @@ namespace System.Management.Automation
|
||||
}
|
||||
}
|
||||
|
||||
#endregion public_methods
|
||||
#endregion public_methods
|
||||
|
||||
#region helper_methods
|
||||
#region helper_methods
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,14 +34,31 @@ namespace System.Management.Automation
|
||||
/// The metadata about the command to run.
|
||||
/// </param>
|
||||
///
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ArgumentTypeConverterAttribute>())
|
||||
foreach (var attribute in runtimeDefinedParameter.Attributes.OfType<ArgumentTypeConverterAttribute>())
|
||||
{
|
||||
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<ArgumentTransformationAttribute> argTransformationAttributes,
|
||||
ref string[] aliases)
|
||||
{
|
||||
// NTRAID#Windows Out Of Band Releases-926374-2005/12/22-JonN
|
||||
if (attribute == null)
|
||||
return;
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Support experimental features in PowerShell
|
||||
/// </summary>
|
||||
public class ExperimentalFeature
|
||||
{
|
||||
#region Const Members
|
||||
|
||||
internal const string EngineSource = "PSEngine";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Instance Members
|
||||
|
||||
/// <summary>
|
||||
/// Name of an experimental feature.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Description of an experimental feature.
|
||||
/// </summary>
|
||||
public string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Source of an experimental feature.
|
||||
/// </summary>
|
||||
public string Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicate whether the feature is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for ExperimentalFeature.
|
||||
/// </summary>
|
||||
internal ExperimentalFeature(string name, string description, string source, bool isEnabled)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
Source = source;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Members
|
||||
|
||||
/// <summary>
|
||||
/// All available engine experimental features.
|
||||
/// </summary>
|
||||
internal static readonly ReadOnlyCollection<ExperimentalFeature> EngineExperimentalFeatures;
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary of all available engine experimental features. Feature name is the key.
|
||||
/// </summary>
|
||||
internal static readonly ReadOnlyDictionary<string, ExperimentalFeature> EngineExperimentalFeatureMap;
|
||||
|
||||
/// <summary>
|
||||
/// Experimental feature names that are enabled in the config file.
|
||||
/// </summary>
|
||||
internal static readonly ImmutableHashSet<string> EnabledExperimentalFeatureNames;
|
||||
|
||||
/// <summary>
|
||||
/// Type initializer. Initialize the engine experimental feature list.
|
||||
/// </summary>
|
||||
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<ExperimentalFeature>(engineFeatures);
|
||||
|
||||
// Initialize the readonly dictionary 'EngineExperimentalFeatureMap'.
|
||||
var engineExpFeatureMap = engineFeatures.ToDictionary(f => f.Name, StringComparer.OrdinalIgnoreCase);
|
||||
EngineExperimentalFeatureMap = new ReadOnlyDictionary<string, ExperimentalFeature>(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<string>();
|
||||
try { enabledFeatures = PowerShellConfig.Instance.GetExperimentalFeatures(); }
|
||||
catch (Exception ex) when (LogException(ex, "Placeholder")) { }
|
||||
|
||||
EnabledExperimentalFeatureNames = ProcessEnabledFeatures(enabledFeatures);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the specified experimental feature has been enabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool HasEnabled(string featureName)
|
||||
{
|
||||
return EnabledExperimentalFeatureNames.Contains(featureName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine the action to take for the specified experiment name and action.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the name follows the engine experimental feature name convention.
|
||||
/// Convention: prefix 'PS' to the feature name -- PSFeatureName
|
||||
/// </summary>
|
||||
internal static bool IsEngineFeatureName(string featureName)
|
||||
{
|
||||
return featureName.IndexOf('.') == -1 && featureName.Length > 2 && featureName.StartsWith("PS", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the name follows the module experimental feature name convention.
|
||||
/// Convention: ModuleName.FeatureName
|
||||
/// </summary>
|
||||
/// <param name="featureName">The feature name to check.</param>
|
||||
/// <param name="moduleName">When specified, we check if the feature name matches the module name</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log the exception without rewinding the stack.
|
||||
/// </summary>
|
||||
private static bool LogException(Exception ex, string errorTemplate)
|
||||
{
|
||||
// TODO: Logging
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log an error message.
|
||||
/// </summary>
|
||||
private static void LogError(string message)
|
||||
{
|
||||
// TODO: Logging
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private static ImmutableHashSet<string> ProcessEnabledFeatures(string[] enabledFeatures)
|
||||
{
|
||||
if (enabledFeatures.Length == 0) { return ImmutableHashSet.Create<string>(); }
|
||||
|
||||
var list = new List<string>(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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the action to take on the cmdlet/parameter that has the attribute declared.
|
||||
/// </summary>
|
||||
public enum ExperimentAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent an undefined action, used as the default value.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Hide the cmdlet/parameter when the corresponding experimental feature is enabled.
|
||||
/// </summary>
|
||||
Hide = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Show the cmdlet/parameter when the corresponding experimental feature is enabled.
|
||||
/// </summary>
|
||||
Show = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The attribute that applies to cmdlet/function/parameter to define what the engine should do with it.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public sealed class ExperimentalAttribute : ParsingBaseAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Specify the experimental feature this attribute is associated with.
|
||||
/// </summary>
|
||||
public string ExperimentName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Specify the action for engine to take when the experimental feature is enabled.
|
||||
/// </summary>
|
||||
public ExperimentAction ExperimentAction { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the ExperimentalAttribute class.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize an instance that represents the none-value.
|
||||
/// </summary>
|
||||
private ExperimentalAttribute() {}
|
||||
|
||||
/// <summary>
|
||||
/// An instance that represents the none-value.
|
||||
/// </summary>
|
||||
internal readonly static ExperimentalAttribute None = new ExperimentalAttribute();
|
||||
|
||||
internal bool ToHide => EffectiveAction == ExperimentAction.Hide;
|
||||
internal bool ToShow => EffectiveAction == ExperimentAction.Show;
|
||||
|
||||
/// <summary>
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
@@ -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<string, SessionStateCmdletEntry> cmdlets, out Dictionary<string, SessionStateProviderEntry> 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<T>(Type decoratedType) where T : Attribute
|
||||
private static bool GetCustomAttribute<T>(Type decoratedType, out T attribute) where T : Attribute
|
||||
{
|
||||
var attributes = decoratedType.GetCustomAttributes<T>(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<T>(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<string, Tuple<SessionStateCmdletEntry, List<SessionStateAliasEntry>>> cachedCmdlets;
|
||||
if (s_cmdletCache.Value.TryGetValue(assembly, out cachedCmdlets))
|
||||
{
|
||||
cmdlets = new Dictionary<string, SessionStateCmdletEntry>(s_cmdletCache.Value.Count, StringComparer.OrdinalIgnoreCase);
|
||||
aliases = new Dictionary<string, List<SessionStateAliasEntry>>(StringComparer.OrdinalIgnoreCase);
|
||||
cmdlets = new Dictionary<string, SessionStateCmdletEntry>(cachedCmdlets.Count, StringComparer.OrdinalIgnoreCase);
|
||||
aliases = new Dictionary<string, List<SessionStateAliasEntry>>(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<string, SessionStateCmdletEntry> cmdletsCheck = null;
|
||||
Dictionary<string, SessionStateProviderEntry> providersCheck = null;
|
||||
Dictionary<string, List<SessionStateAliasEntry>> 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<string, SessionStateCmdletEntry> cmdlets,
|
||||
ref Dictionary<string, List<SessionStateAliasEntry>> aliases,
|
||||
ref Dictionary<string, SessionStateProviderEntry> 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<CmdletAttribute>(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<string, SessionStateCmdletEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
if (psSnapInInfo != null) { cmdlet.SetPSSnapIn(psSnapInInfo); }
|
||||
if (moduleInfo != null) { cmdlet.SetModule(moduleInfo); }
|
||||
|
||||
cmdlets = cmdlets ?? new Dictionary<string, SessionStateCmdletEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
cmdlets.Add(cmdletName, cmdlet);
|
||||
|
||||
var aliasAttribute = GetCustomAttribute<AliasAttribute>(type);
|
||||
if (aliasAttribute != null)
|
||||
if (GetCustomAttribute(type, out AliasAttribute aliasAttribute))
|
||||
{
|
||||
if (aliases == null)
|
||||
{
|
||||
aliases = new Dictionary<string, List<SessionStateAliasEntry>>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
aliases = aliases ?? new Dictionary<string, List<SessionStateAliasEntry>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var aliasList = new List<SessionStateAliasEntry>();
|
||||
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<CmdletProviderAttribute>(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<string, SessionStateProviderEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
providers = providers ?? new Dictionary<string, SessionStateProviderEntry>(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<Type> 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<Type> 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<ConcurrentDictionary<Assembly, Dictionary<string, Tuple<SessionStateCmdletEntry, List<SessionStateAliasEntry>>>>>();
|
||||
private static Lazy<ConcurrentDictionary<Assembly, Dictionary<string, SessionStateProviderEntry>>> s_providerCache =
|
||||
new Lazy<ConcurrentDictionary<Assembly, Dictionary<string, SessionStateProviderEntry>>>();
|
||||
// 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<ConcurrentDictionary<Assembly, bool>> s_assembliesWithModuleInitializerCache = new Lazy<ConcurrentDictionary<Assembly, bool>>();
|
||||
|
||||
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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -193,6 +193,14 @@ namespace System.Management.Automation.Configuration
|
||||
WriteValueToFile<bool>(ConfigScope.SystemWide, "DisablePromptToUpdateHelp", prompt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the names of experimental features enabled in the config file.
|
||||
/// </summary>
|
||||
internal string[] GetExperimentalFeatures()
|
||||
{
|
||||
return ReadValueFromFile<string[]>(ConfigScope.SystemWide, "ExperimentalFeatures", Utils.EmptyArray<string>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Corresponding settings of the original Group Policies
|
||||
/// </summary>
|
||||
|
||||
@@ -164,7 +164,36 @@ namespace System.Management.Automation
|
||||
/// This can be any attribute that can be applied to a normal parameter.
|
||||
/// </remarks>
|
||||
public Collection<Attribute> Attributes { get; } = new Collection<Attribute>();
|
||||
} // class RuntimeDefinedParameter
|
||||
|
||||
/// <summary>
|
||||
/// Check if the parameter is disabled due to the associated experimental feature.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a collection of runtime-defined parameters that are keyed based on the name
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Attribute> in CoreCLR
|
||||
var attributes = member.GetCustomAttributes(typeof(ParameterAttribute), false);
|
||||
if (attributes.Any())
|
||||
var expAttribute = member.GetCustomAttributes<ExperimentalAttribute>(false).FirstOrDefault();
|
||||
if (expAttribute != null && expAttribute.ToHide) { return false; }
|
||||
|
||||
var hasAnyVisibleParamAttributes = false;
|
||||
var paramAttributes = member.GetCustomAttributes<ParameterAttribute>(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
|
||||
|
||||
@@ -1001,15 +1001,17 @@ namespace System.Management.Automation.Language
|
||||
internal static RuntimeDefinedParameterDictionary GetParameterMetaData(ReadOnlyCollection<ParameterAst> parameters, bool automaticPositions, ref bool usesCmdletBinding)
|
||||
{
|
||||
var md = new RuntimeDefinedParameterDictionary();
|
||||
var listMd = new List<RuntimeDefinedParameter>();
|
||||
var listMd = new List<RuntimeDefinedParameter>(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<Attribute> attributes = new List<Attribute>();
|
||||
List<Attribute> attributes = new List<Attribute>(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<Attribute>(attributes.ToArray()));
|
||||
new Collection<Attribute>(attributes));
|
||||
|
||||
if (parameterAst.DefaultValue != null)
|
||||
{
|
||||
|
||||
@@ -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" } },
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace System.Management.Automation.Language
|
||||
bool HasAnyScriptBlockAttributes();
|
||||
RuntimeDefinedParameterDictionary GetParameterMetadata(bool automaticPositions, ref bool usesCmdletBinding);
|
||||
IEnumerable<Attribute> GetScriptBlockAttributes();
|
||||
IEnumerable<ExperimentalAttribute> GetExperimentalAttributes();
|
||||
|
||||
bool UsesCmdletBinding();
|
||||
ReadOnlyCollection<ParameterAst> Parameters { get; }
|
||||
@@ -1370,6 +1371,61 @@ namespace System.Management.Automation.Language
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<ExperimentalAttribute> 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<ParameterAst> IParameterMetadataProvider.Parameters
|
||||
{
|
||||
get { return (ParamBlock != null) ? this.ParamBlock.Parameters : null; }
|
||||
@@ -3299,6 +3355,11 @@ namespace System.Management.Automation.Language
|
||||
return ((IParameterMetadataProvider)_functionDefinitionAst).GetScriptBlockAttributes();
|
||||
}
|
||||
|
||||
IEnumerable<ExperimentalAttribute> 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<ExperimentalAttribute> GetExperimentalAttributes()
|
||||
{
|
||||
return ((IParameterMetadataProvider)Body).GetExperimentalAttributes();
|
||||
}
|
||||
|
||||
public bool UsesCmdletBinding()
|
||||
{
|
||||
return false;
|
||||
@@ -3693,6 +3759,11 @@ namespace System.Management.Automation.Language
|
||||
return ((IParameterMetadataProvider)Body).GetScriptBlockAttributes();
|
||||
}
|
||||
|
||||
IEnumerable<ExperimentalAttribute> IParameterMetadataProvider.GetExperimentalAttributes()
|
||||
{
|
||||
return ((IParameterMetadataProvider)Body).GetExperimentalAttributes();
|
||||
}
|
||||
|
||||
ReadOnlyCollection<ParameterAst> IParameterMetadataProvider.Parameters
|
||||
{
|
||||
get { return Parameters ?? (Body.ParamBlock != null ? Body.ParamBlock.Parameters : null); }
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -218,4 +218,10 @@ The #requires statement must be in one of the following formats:
|
||||
<data name="GetCommandShowCommandInfoParamError" xml:space="preserve">
|
||||
<value>The ShowCommandInfo and Syntax parameters cannot be specified together.</value>
|
||||
</data>
|
||||
<data name="ScriptDisabledWhenFeatureOn" xml:space="preserve">
|
||||
<value>This script command is disabled when the experimental feature '{0}' has been turned on.</value>
|
||||
</data>
|
||||
<data name="ScriptDisabledWhenFeatureOff" xml:space="preserve">
|
||||
<value>This script command is disabled when the experimental feature '{0}' has been turned off.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Reference in New Issue
Block a user