diff --git a/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.man b/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.man
index 33c0a5f519..491e4a8746 100644
--- a/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.man
+++ b/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.man
@@ -98,6 +98,29 @@
value="0xD003"
version="1"
/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ViewsOf_System_Management_Automation_ExperimentalFeature()
+ {
+ yield return new FormatViewDefinition("ExperimentalFeature",
+ TableControl.Create()
+ .AddHeader(Alignment.Left, width: 35)
+ .AddHeader(Alignment.Right, width: 10)
+ .AddHeader(Alignment.Left, width: 35)
+ .AddHeader(Alignment.Left)
+ .StartRowDefinition()
+ .AddPropertyColumn("Name")
+ .AddPropertyColumn("Enabled")
+ .AddPropertyColumn("Source")
+ .AddPropertyColumn("Description")
+ .EndRowDefinition()
+ .EndTable());
+
+ yield return new FormatViewDefinition("ExperimentalFeature",
+ ListControl.Create()
+ .StartEntry()
+ .AddItemProperty("Name")
+ .AddItemProperty("Enabled")
+ .AddItemProperty("Source")
+ .AddItemProperty("Description")
+ .EndEntry()
+ .EndList());
+ }
+
private static IEnumerable ViewsOf_Microsoft_PowerShell_Commands_BasicHtmlWebResponseObject()
{
yield return new FormatViewDefinition("Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject",
diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
index 7a76b0075b..cabccc1ce2 100644
--- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
+++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
@@ -7,6 +7,7 @@ using System.Collections.Immutable;
using System.Linq;
using System.Management.Automation.Configuration;
using System.Management.Automation.Internal;
+using System.Management.Automation.Tracing;
using System.Runtime.CompilerServices;
namespace System.Management.Automation
@@ -42,7 +43,7 @@ namespace System.Management.Automation
///
/// Indicate whether the feature is enabled.
///
- public bool IsEnabled { get; private set; }
+ public bool Enabled { get; private set; }
///
/// Constructor for ExperimentalFeature.
@@ -52,7 +53,7 @@ namespace System.Management.Automation
Name = name;
Description = description;
Source = source;
- IsEnabled = isEnabled;
+ Enabled = isEnabled;
}
#endregion
@@ -85,7 +86,7 @@ namespace System.Management.Automation
new ExperimentalFeature(name: "PSFileSystemProviderV2",
description: "Replace the old FileSystemProvider with cleaner design and faster code",
source: EngineSource,
- isEnabled: false))
+ isEnabled: false)),
*/
};
EngineExperimentalFeatures = new ReadOnlyCollection(engineFeatures);
@@ -102,39 +103,64 @@ namespace System.Management.Automation
// 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")) { }
+ try { enabledFeatures = PowerShellConfig.Instance.GetExperimentalFeatures(); } catch (Exception e) when (LogException(e)) { }
EnabledExperimentalFeatureNames = ProcessEnabledFeatures(enabledFeatures);
}
///
- /// Check if the specified experimental feature has been enabled.
+ /// 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.
///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool HasEnabled(string featureName)
+ private static ImmutableHashSet ProcessEnabledFeatures(string[] enabledFeatures)
{
- return EnabledExperimentalFeatureNames.Contains(featureName);
+ 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.Enabled = true;
+ list.Add(name);
+ }
+ else
+ {
+ string message = StringUtil.Format(Logging.EngineExperimentalFeatureNotFound, name);
+ LogError(PSEventId.ExperimentalFeature_InvalidName, name, message);
+ }
+ }
+ else
+ {
+ string message = StringUtil.Format(Logging.InvalidExperimentalFeatureName, name);
+ LogError(PSEventId.ExperimentalFeature_InvalidName, name, message);
+ }
+ }
+ return ImmutableHashSet.CreateRange(StringComparer.OrdinalIgnoreCase, list);
}
///
- /// Determine the action to take for the specified experiment name and action.
+ /// Log the exception without rewinding the stack.
///
- internal static ExperimentAction GetActionToTake(string experimentName, ExperimentAction experimentAction)
+ private static bool LogException(Exception e)
{
- 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;
- }
+ LogError(PSEventId.ExperimentalFeature_ReadConfig_Error, e.GetType().FullName, e.Message, e.StackTrace);
+ return false;
+ }
- ExperimentAction action = experimentAction;
- if (!HasEnabled(experimentName))
- {
- action = (action == ExperimentAction.Hide) ? ExperimentAction.Show : ExperimentAction.Hide;
- }
- return action;
+ ///
+ /// Log an error message.
+ ///
+ private static void LogError(PSEventId eventId, params object[] args)
+ {
+ PSEtwLog.LogOperationalError(eventId, PSOpcode.Constructor, PSTask.ExperimentalFeature, PSKeyword.UseAlwaysOperational, args);
}
///
@@ -167,56 +193,32 @@ namespace System.Management.Automation
}
///
- /// Log the exception without rewinding the stack.
+ /// Determine the action to take for the specified experiment name and action.
///
- private static bool LogException(Exception ex, string errorTemplate)
+ internal static ExperimentAction GetActionToTake(string experimentName, ExperimentAction experimentAction)
{
- // 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 (experimentName == null || experimentAction == ExperimentAction.None)
{
- 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");
- }
+ // 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;
}
- return ImmutableHashSet.CreateRange(StringComparer.OrdinalIgnoreCase, list);
+
+ ExperimentAction action = experimentAction;
+ if (!HasEnabled(experimentName))
+ {
+ action = (action == ExperimentAction.Hide) ? ExperimentAction.Show : ExperimentAction.Hide;
+ }
+ return action;
+ }
+
+ ///
+ /// Check if the specified experimental feature has been enabled.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool HasEnabled(string featureName)
+ {
+ return EnabledExperimentalFeatureNames.Contains(featureName);
}
#endregion
diff --git a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs
index 79ec351b28..412b3446db 100644
--- a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs
+++ b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs
@@ -418,7 +418,7 @@ namespace Microsoft.PowerShell.Commands
///
/// Extra variables that are allowed to be referenced in module manifest file
///
- private static readonly string[] s_extraAllowedVariables = new string[] { "PSScriptRoot", "PSEdition" };
+ private static readonly string[] s_extraAllowedVariables = new string[] { SpecialVariables.PSScriptRoot, SpecialVariables.PSEdition, SpecialVariables.EnabledExperimentalFeatures };
///
/// Load and execute the manifest psd1 file or a localized manifest psd1 file.
diff --git a/src/System.Management.Automation/engine/SessionState.cs b/src/System.Management.Automation/engine/SessionState.cs
index 4f66c42110..311b904ccb 100644
--- a/src/System.Management.Automation/engine/SessionState.cs
+++ b/src/System.Management.Automation/engine/SessionState.cs
@@ -363,13 +363,18 @@ namespace System.Management.Automation
this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true);
// $PSHOME
- // This depends on the shellId. If we cannot read the application base
- // registry key, set the variable to empty string
string applicationBase = Utils.DefaultPowerShellAppBase;
v = new PSVariable(SpecialVariables.PSHome, applicationBase,
ScopedItemOptions.Constant | ScopedItemOptions.AllScope,
RunspaceInit.PSHOMEDescription);
this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true);
+
+ // $EnabledExperimentalFeatures
+ v = new PSVariable(SpecialVariables.EnabledExperimentalFeatures,
+ ExperimentalFeature.EnabledExperimentalFeatureNames,
+ ScopedItemOptions.Constant | ScopedItemOptions.AllScope,
+ RunspaceInit.EnabledExperimentalFeatures);
+ this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true);
}
///
diff --git a/src/System.Management.Automation/engine/SpecialVariables.cs b/src/System.Management.Automation/engine/SpecialVariables.cs
index f1a2c32315..c95689e5ba 100644
--- a/src/System.Management.Automation/engine/SpecialVariables.cs
+++ b/src/System.Management.Automation/engine/SpecialVariables.cs
@@ -225,20 +225,7 @@ namespace System.Management.Automation
internal const string PSVersionTable = "PSVersionTable";
internal const string PSEdition = "PSEdition";
internal const string ShellId = "ShellId";
-
- internal static List AllScopeSessionVariables = new List
- {
- ExecutionContext,
- Home,
- Host,
- PID,
- PSCulture,
- PSHome,
- PSUICulture,
- PSVersionTable,
- PSEdition,
- ShellId
- };
+ internal const string EnabledExperimentalFeatures = "EnabledExperimentalFeatures";
#endregion AllScope variables created in every session
@@ -304,6 +291,7 @@ namespace System.Management.Automation
SpecialVariables.PSEdition,
SpecialVariables.ShellId,
SpecialVariables.True,
+ SpecialVariables.EnabledExperimentalFeatures,
};
private static readonly HashSet s_classMethodsAccessibleVariables = new HashSet
diff --git a/src/System.Management.Automation/engine/parser/TypeResolver.cs b/src/System.Management.Automation/engine/parser/TypeResolver.cs
index d347457358..2be4113385 100644
--- a/src/System.Management.Automation/engine/parser/TypeResolver.cs
+++ b/src/System.Management.Automation/engine/parser/TypeResolver.cs
@@ -731,8 +731,10 @@ namespace System.Management.Automation
{ typeof(DateTime), new[] { "datetime" } },
{ typeof(decimal), new[] { "decimal" } },
{ typeof(double), new[] { "double" } },
- { typeof(DscResourceAttribute), new[] { "DscResource"} },
+ { typeof(DscResourceAttribute), new[] { "DscResource" } },
+ { typeof(ExperimentAction), new[] { "ExperimentAction" } },
{ typeof(ExperimentalAttribute), new[] { "Experimental" } },
+ { typeof(ExperimentalFeature), new[] { "ExperimentalFeature" } },
{ typeof(float), new[] { "float", "single" } },
{ typeof(Guid), new[] { "guid" } },
{ typeof(Hashtable), new[] { "hashtable" } },
diff --git a/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs b/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs
index 05164306f5..b898725769 100644
--- a/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs
+++ b/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs
@@ -159,6 +159,10 @@ namespace System.Management.Automation.Internal
Settings = 0x1F04,
Engine_Trace = 0x1F06,
+ // Experimental Features
+ ExperimentalFeature_InvalidName = 0x3001,
+ ExperimentalFeature_ReadConfig_Error = 0x3002,
+
// Scheduled Jobs
ScheduledJob_Start = 0xD001,
ScheduledJob_Complete = 0xD002,
@@ -232,6 +236,7 @@ namespace System.Management.Automation.Internal
ProviderStart = 0x68,
ProviderStop = 0x69,
ExecutePipeline = 0x6A,
+ ExperimentalFeature = 0x6B,
ScheduledJob = 0x6E,
NamedPipe = 0x6F,
ISEOperation = 0x78
diff --git a/src/System.Management.Automation/resources/Logging.resx b/src/System.Management.Automation/resources/Logging.resx
index 7a4d98e500..b8cb1b2947 100644
--- a/src/System.Management.Automation/resources/Logging.resx
+++ b/src/System.Management.Automation/resources/Logging.resx
@@ -288,4 +288,13 @@ AdditionalInfo:
UNKNOWN
+
+ The engine experimental feature '{0}' declared in the config file is not registered in the current PowerShell.
+
+
+ The experimental feature '{0}' declared in the config file is invalid.
+The name of an experimental feature should follow the convention below:
+ Engine Feature Name: 'PS[FeatureName]'
+ Module Feature Name: '[ModuleName].[FeatureName]'
+
diff --git a/src/System.Management.Automation/resources/RunspaceInit.resx b/src/System.Management.Automation/resources/RunspaceInit.resx
index b8aee86a25..c4a485121c 100644
--- a/src/System.Management.Automation/resources/RunspaceInit.resx
+++ b/src/System.Management.Automation/resources/RunspaceInit.resx
@@ -117,6 +117,9 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ Variable to hold the enabled experimental feature names
+
Parent folder of the host application of the current runspace