Add logging, formatting, and '$EnabledExperimentalFeatures'

This commit is contained in:
Dongbo Wang
2018-07-06 13:28:19 -07:00
parent 7770aa547e
commit dc2d741791
10 changed files with 197 additions and 87 deletions
@@ -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"
@@ -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",
@@ -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>