mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Add logging, formatting, and '$EnabledExperimentalFeatures'
This commit is contained in:
@@ -98,6 +98,29 @@
|
||||
value="0xD003"
|
||||
version="1"
|
||||
/>
|
||||
<!--ExperimentalFeature event-->
|
||||
<event
|
||||
channel="C_OPERATIONAL"
|
||||
level="win:Error"
|
||||
message="$(string.PS_PROVIDER.event.E_O_ExperimentalFeatureInvalidName.message)"
|
||||
opcode="Constructor"
|
||||
symbol="ExperimentalFeatureInvalidName"
|
||||
task="ExperimentalFeature"
|
||||
template="T_ExperimentalFeatureInvalidName"
|
||||
value="0x3001"
|
||||
version="1"
|
||||
/>
|
||||
<event
|
||||
channel="C_OPERATIONAL"
|
||||
level="win:Error"
|
||||
message="$(string.PS_PROVIDER.event.E_O_ExperimentalFeatureReadConfigError.message)"
|
||||
opcode="Constructor"
|
||||
symbol="ExperimentalFeatureReadConfigError"
|
||||
task="ExperimentalFeature"
|
||||
template="T_ExperimentalFeatureReadConfigError"
|
||||
value="0x3002"
|
||||
version="1"
|
||||
/>
|
||||
<!--M3P events-->
|
||||
<event
|
||||
channel="C_ANALYTIC"
|
||||
@@ -2368,6 +2391,12 @@
|
||||
symbol="T_EXECUTEPIEPLINE"
|
||||
value="106"
|
||||
/>
|
||||
<task
|
||||
message="$(string.PS_PROVIDER.task.T_ExperimentalFeature.message)"
|
||||
name="ExperimentalFeature"
|
||||
symbol="T_EXPERIMENTALFEATURE"
|
||||
value="107"
|
||||
/>
|
||||
<task
|
||||
message="$(string.PS_PROVIDER.task.T_ScheduledJob.message)"
|
||||
name="ScheduledJob"
|
||||
@@ -3927,6 +3956,30 @@
|
||||
name="InnerException"
|
||||
/>
|
||||
</template>
|
||||
<template tid="T_ExperimentalFeatureInvalidName">
|
||||
<data
|
||||
inType="win:UnicodeString"
|
||||
name="Name"
|
||||
/>
|
||||
<data
|
||||
inType="win:UnicodeString"
|
||||
name="Message"
|
||||
/>
|
||||
</template>
|
||||
<template tid="T_ExperimentalFeatureReadConfigError">
|
||||
<data
|
||||
inType="win:UnicodeString"
|
||||
name="Name"
|
||||
/>
|
||||
<data
|
||||
inType="win:UnicodeString"
|
||||
name="Message"
|
||||
/>
|
||||
<data
|
||||
inType="win:UnicodeString"
|
||||
name="StackTrace"
|
||||
/>
|
||||
</template>
|
||||
<template tid="T_TrackingGuid">
|
||||
<data
|
||||
inType="win:GUID"
|
||||
@@ -5428,6 +5481,18 @@
|
||||
id="PS_PROVIDER.task.T_ScheduledJob.message"
|
||||
value="PowerShell Scheduled Jobs"
|
||||
/>
|
||||
<string
|
||||
id="PS_PROVIDER.event.E_O_ExperimentalFeatureInvalidName.message"
|
||||
value="Experimental Feature Initialization: Ignore the experimental feature %1 from the config file. %2"
|
||||
/>
|
||||
<string
|
||||
id="PS_PROVIDER.event.E_O_ExperimentalFeatureReadConfigError.message"
|
||||
value="Experimental Feature Initialization: Failed to read the config file.%n Exception %1: %n Message: %2 %n StackTrace: %3 %n"
|
||||
/>
|
||||
<string
|
||||
id="PS_PROVIDER.task.T_ExperimentalFeature.message"
|
||||
value="PowerShell Experimental Features"
|
||||
/>
|
||||
<string
|
||||
id="PS_PROVIDER.task.T_NamedPipe.message"
|
||||
value="PowerShell Named Pipe IPC"
|
||||
|
||||
+31
@@ -230,6 +230,10 @@ namespace System.Management.Automation.Runspaces
|
||||
"System.Management.Automation.PSModuleInfo",
|
||||
ViewsOf_System_Management_Automation_PSModuleInfo());
|
||||
|
||||
yield return new ExtendedTypeDefinition(
|
||||
"System.Management.Automation.ExperimentalFeature",
|
||||
ViewsOf_System_Management_Automation_ExperimentalFeature());
|
||||
|
||||
var td46 = new ExtendedTypeDefinition(
|
||||
"Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject",
|
||||
ViewsOf_Microsoft_PowerShell_Commands_BasicHtmlWebResponseObject());
|
||||
@@ -1240,6 +1244,33 @@ namespace System.Management.Automation.Runspaces
|
||||
.EndList());
|
||||
}
|
||||
|
||||
private static IEnumerable<FormatViewDefinition> 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<FormatViewDefinition> ViewsOf_Microsoft_PowerShell_Commands_BasicHtmlWebResponseObject()
|
||||
{
|
||||
yield return new FormatViewDefinition("Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject",
|
||||
|
||||
+71
-69
@@ -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
|
||||
/// <summary>
|
||||
/// Indicate whether the feature is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; private set; }
|
||||
public bool Enabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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<ExperimentalFeature>(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<string>();
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool HasEnabled(string featureName)
|
||||
private static ImmutableHashSet<string> ProcessEnabledFeatures(string[] enabledFeatures)
|
||||
{
|
||||
return EnabledExperimentalFeatureNames.Contains(featureName);
|
||||
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.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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine the action to take for the specified experiment name and action.
|
||||
/// Log the exception without rewinding the stack.
|
||||
/// </summary>
|
||||
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;
|
||||
/// <summary>
|
||||
/// Log an error message.
|
||||
/// </summary>
|
||||
private static void LogError(PSEventId eventId, params object[] args)
|
||||
{
|
||||
PSEtwLog.LogOperationalError(eventId, PSOpcode.Constructor, PSTask.ExperimentalFeature, PSKeyword.UseAlwaysOperational, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -167,56 +193,32 @@ namespace System.Management.Automation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log the exception without rewinding the stack.
|
||||
/// Determine the action to take for the specified experiment name and action.
|
||||
/// </summary>
|
||||
private static bool LogException(Exception ex, string errorTemplate)
|
||||
internal static ExperimentAction GetActionToTake(string experimentName, ExperimentAction experimentAction)
|
||||
{
|
||||
// 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 (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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the specified experimental feature has been enabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool HasEnabled(string featureName)
|
||||
{
|
||||
return EnabledExperimentalFeatureNames.Contains(featureName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -418,7 +418,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// <summary>
|
||||
/// Extra variables that are allowed to be referenced in module manifest file
|
||||
/// </summary>
|
||||
private static readonly string[] s_extraAllowedVariables = new string[] { "PSScriptRoot", "PSEdition" };
|
||||
private static readonly string[] s_extraAllowedVariables = new string[] { SpecialVariables.PSScriptRoot, SpecialVariables.PSEdition, SpecialVariables.EnabledExperimentalFeatures };
|
||||
|
||||
/// <summary>
|
||||
/// Load and execute the manifest psd1 file or a localized manifest psd1 file.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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<string> AllScopeSessionVariables = new List<string>
|
||||
{
|
||||
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<string> s_classMethodsAccessibleVariables = new HashSet<string>
|
||||
|
||||
@@ -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" } },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -288,4 +288,13 @@ AdditionalInfo:
|
||||
<data name="UnknownUserName" xml:space="preserve">
|
||||
<value>UNKNOWN</value>
|
||||
</data>
|
||||
<data name="EngineExperimentalFeatureNotFound" xml:space="preserve">
|
||||
<value>The engine experimental feature '{0}' declared in the config file is not registered in the current PowerShell.</value>
|
||||
</data>
|
||||
<data name="InvalidExperimentalFeatureName" xml:space="preserve">
|
||||
<value>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]'</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -117,6 +117,9 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="EnabledExperimentalFeatures" xml:space="preserve">
|
||||
<value>Variable to hold the enabled experimental feature names</value>
|
||||
</data>
|
||||
<data name="PSHOMEDescription" xml:space="preserve">
|
||||
<value>Parent folder of the host application of the current runspace</value>
|
||||
</data>
|
||||
|
||||
Reference in New Issue
Block a user