Add 'Get-ExperimentalFeature' cmdlet

This commit is contained in:
Dongbo Wang
2018-07-06 13:28:19 -07:00
parent 8a5ddbb231
commit 7770aa547e
7 changed files with 251 additions and 49 deletions
@@ -286,7 +286,7 @@ namespace System.Management.Automation
/// <summary>
/// An instance that represents the none-value.
/// </summary>
internal readonly static ExperimentalAttribute None = new ExperimentalAttribute();
internal static readonly ExperimentalAttribute None = new ExperimentalAttribute();
internal bool ToHide => EffectiveAction == ExperimentAction.Hide;
internal bool ToShow => EffectiveAction == ExperimentAction.Show;
@@ -0,0 +1,180 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Internal;
namespace Microsoft.PowerShell.Commands
{
/// <summary>
/// Implements Get-ExperimentalFeature cmdlet.
/// </summary>
[Cmdlet(VerbsCommon.Get, "ExperimentalFeature", HelpUri = "")]
public class GetExperimentalFeatureCommand : PSCmdlet
{
/// <summary>
/// Specify the feature names.
/// </summary>
[Parameter(ValueFromPipeline = true, Position = 0)]
[ValidateNotNullOrEmpty]
public string[] Name { get; set; }
/// <summary>
/// Search module paths to find all available experimental features.
/// </summary>
[Parameter()]
public SwitchParameter ListAvailable { get; set; }
/// <summary>
/// ProcessRecord method of this cmdlet.
/// </summary>
protected override void ProcessRecord()
{
const WildcardOptions wildcardOptions = WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant;
IEnumerable<WildcardPattern> namePatterns = SessionStateUtilities.CreateWildcardsFromStrings(Name, wildcardOptions);
if (ListAvailable)
{
foreach (ExperimentalFeature feature in GetAvailableExperimentalFeatures(namePatterns).OrderBy(GetSortingString))
{
WriteObject(feature);
}
}
else if (ExperimentalFeature.EnabledExperimentalFeatureNames.Count > 0)
{
foreach (ExperimentalFeature feature in GetEnabledExperimentalFeatures(namePatterns).OrderBy(GetSortingString))
{
WriteObject(feature);
}
}
}
/// <summary>
/// Construct the string for sorting experimental feature records.
/// </summary>
/// <remarks>
/// Engine features come before module features.
/// Within engine features and more features, features are ordered by name.
/// </remarks>
private static string GetSortingString(ExperimentalFeature feature)
{
if (ExperimentalFeature.EngineSource.Equals(feature.Source))
{
return "0:" + feature.Name;
}
else
{
return "1:" + feature.Name;
}
}
/// <summary>
/// Get enabled experimental features based on the specified name patterns.
/// </summary>
private IEnumerable<ExperimentalFeature> GetEnabledExperimentalFeatures(IEnumerable<WildcardPattern> namePatterns)
{
var moduleFeatures = new List<string>();
var moduleNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (string featureName in ExperimentalFeature.EnabledExperimentalFeatureNames)
{
// Only process the feature names that matches any name patterns.
if (SessionStateUtilities.MatchesAnyWildcardPattern(featureName, namePatterns, defaultValue: true))
{
if (ExperimentalFeature.EngineExperimentalFeatureMap.TryGetValue(featureName, out ExperimentalFeature feature))
{
yield return feature;
}
else
{
moduleFeatures.Add(featureName);
int lastDotIndex = featureName.LastIndexOf('.');
moduleNames.Add(featureName.Substring(0, lastDotIndex));
}
}
}
if (moduleFeatures.Count > 0)
{
var featuresFromGivenModules = new Dictionary<string, ExperimentalFeature>(StringComparer.OrdinalIgnoreCase);
foreach (string moduleFile in GetValidModuleFiles(moduleNames))
{
ExperimentalFeature[] features = ModuleIntrinsics.GetExperimentalFeature(moduleFile);
foreach (var feature in features)
{
featuresFromGivenModules.TryAdd(feature.Name, feature);
}
}
foreach (string featureName in moduleFeatures)
{
if (featuresFromGivenModules.TryGetValue(featureName, out ExperimentalFeature feature))
{
yield return feature;
}
else
{
yield return new ExperimentalFeature(featureName, description: null, source: null, isEnabled: true);
}
}
}
}
/// <summary>
/// Get available experimental features based on the specified name patterns.
/// </summary>
private IEnumerable<ExperimentalFeature> GetAvailableExperimentalFeatures(IEnumerable<WildcardPattern> namePatterns)
{
foreach (ExperimentalFeature feature in ExperimentalFeature.EngineExperimentalFeatures)
{
if (SessionStateUtilities.MatchesAnyWildcardPattern(feature.Name, namePatterns, defaultValue: true))
{
yield return feature;
}
}
foreach (string moduleFile in GetValidModuleFiles(moduleNamesToFind: null))
{
ExperimentalFeature[] features = ModuleIntrinsics.GetExperimentalFeature(moduleFile);
foreach (var feature in features)
{
if (SessionStateUtilities.MatchesAnyWildcardPattern(feature.Name, namePatterns, defaultValue: true))
{
yield return feature;
}
}
}
}
/// <summary>
/// Get valid module files from module paths.
/// </summary>
private IEnumerable<string> GetValidModuleFiles(HashSet<string> moduleNamesToFind)
{
var modulePaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (string path in ModuleIntrinsics.GetModulePath(includeSystemModulePath: false, Context))
{
string uniquePath = path.TrimEnd(Utils.Separators.Directory);
if (!modulePaths.Add(uniquePath)) { continue; }
foreach (string moduleFile in ModuleUtils.GetDefaultAvailableModuleFiles(uniquePath))
{
// We only care about module manifest files because that's where experimental features are declared.
if (!moduleFile.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase)) { continue; }
if (moduleNamesToFind != null)
{
string currentModuleName = ModuleIntrinsics.GetModuleName(moduleFile);
if (!moduleNamesToFind.Contains(currentModuleName)) { continue; }
}
yield return moduleFile;
}
}
}
}
}
@@ -5277,6 +5277,7 @@ end
{"Export-ModuleMember", new SessionStateCmdletEntry("Export-ModuleMember", typeof(ExportModuleMemberCommand), helpFile) },
{"ForEach-Object", new SessionStateCmdletEntry("ForEach-Object", typeof(ForEachObjectCommand), helpFile) },
{"Get-Command", new SessionStateCmdletEntry("Get-Command", typeof(GetCommandCommand), helpFile) },
{"Get-ExperimentalFeature", new SessionStateCmdletEntry("Get-ExperimentalFeature", typeof(GetExperimentalFeatureCommand), helpFile) },
{"Get-Help", new SessionStateCmdletEntry("Get-Help", typeof(GetHelpCommand), helpFile) },
{"Get-History", new SessionStateCmdletEntry("Get-History", typeof(GetHistoryCommand), helpFile) },
{"Get-Job", new SessionStateCmdletEntry("Get-Job", typeof(GetJobCommand), helpFile) },
@@ -2059,8 +2059,8 @@ namespace Microsoft.PowerShell.Commands
if (ExperimentalFeature.IsModuleFeatureName(featureName, moduleName))
{
string featureDescription = feature["Description"] as string;
expFeatureList.Add(new ExperimentalFeature(featureName, featureDescription, moduleName,
ExperimentalFeature.HasEnabled(featureName)));
expFeatureList.Add(new ExperimentalFeature(featureName, featureDescription, moduleManifestPath,
ExperimentalFeature.HasEnabled(featureName)));
}
else
{
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Management.Automation.Configuration;
@@ -414,64 +415,92 @@ namespace System.Management.Automation
internal static Version GetManifestModuleVersion(string manifestPath)
{
if (manifestPath != null &&
manifestPath.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase))
try
{
try
{
var dataFileSetting =
PsUtils.GetModuleManifestProperties(
manifestPath,
PsUtils.ManifestModuleVersionPropertyName);
Hashtable dataFileSetting =
PsUtils.GetModuleManifestProperties(
manifestPath,
PsUtils.ManifestModuleVersionPropertyName);
var versionValue = dataFileSetting["ModuleVersion"];
if (versionValue != null)
object versionValue = dataFileSetting["ModuleVersion"];
if (versionValue != null)
{
Version moduleVersion;
if (LanguagePrimitives.TryConvertTo(versionValue, out moduleVersion))
{
Version moduleVersion;
if (LanguagePrimitives.TryConvertTo(versionValue, out moduleVersion))
{
return moduleVersion;
}
return moduleVersion;
}
}
catch (PSInvalidOperationException)
{
}
}
catch (PSInvalidOperationException) { }
return new Version(0, 0);
}
internal static Guid GetManifestGuid(string manifestPath)
{
if (manifestPath != null &&
manifestPath.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase))
try
{
try
{
var dataFileSetting =
PsUtils.GetModuleManifestProperties(
manifestPath,
PsUtils.ManifestGuidPropertyName);
Hashtable dataFileSetting =
PsUtils.GetModuleManifestProperties(
manifestPath,
PsUtils.ManifestGuidPropertyName);
var guidValue = dataFileSetting["GUID"];
if (guidValue != null)
object guidValue = dataFileSetting["GUID"];
if (guidValue != null)
{
Guid guidID;
if (LanguagePrimitives.TryConvertTo(guidValue, out guidID))
{
Guid guidID;
if (LanguagePrimitives.TryConvertTo(guidValue, out guidID))
{
return guidID;
}
return guidID;
}
}
catch (PSInvalidOperationException)
{
}
}
catch (PSInvalidOperationException) { }
return new Guid();
}
internal static ExperimentalFeature[] GetExperimentalFeature(string manifestPath)
{
try
{
Hashtable dataFileSetting =
PsUtils.GetModuleManifestProperties(
manifestPath,
PsUtils.ManifestPrivateDataPropertyName);
object privateData = dataFileSetting["PrivateData"];
if (privateData is Hashtable hashData && hashData["PSData"] is Hashtable psData)
{
object expFeatureValue = psData["ExperimentalFeatures"];
if (expFeatureValue != null &&
LanguagePrimitives.TryConvertTo(expFeatureValue, out Hashtable[] features) &&
features.Length > 0)
{
string moduleName = ModuleIntrinsics.GetModuleName(manifestPath);
var expFeatureList = new List<ExperimentalFeature>();
foreach (Hashtable feature in features)
{
string featureName = feature["Name"] as string;
if (String.IsNullOrEmpty(featureName)) { continue; }
if (ExperimentalFeature.IsModuleFeatureName(featureName, moduleName))
{
string featureDescription = feature["Description"] as string;
expFeatureList.Add(new ExperimentalFeature(featureName, featureDescription, manifestPath,
ExperimentalFeature.HasEnabled(featureName)));
}
}
return expFeatureList.ToArray();
}
}
}
catch (PSInvalidOperationException) { }
return Utils.EmptyArray<ExperimentalFeature>();
}
// The extensions of all of the files that can be processed with Import-Module, put the ni.dll in front of .dll to have higher priority to be loaded.
internal static string[] PSModuleProcessableExtensions = new string[] {
StringLiterals.PowerShellDataFileExtension,
@@ -357,7 +357,6 @@ namespace System.Management.Automation
// $ShellId - if there is no runspace config, use the default string
string shellId = ExecutionContext.ShellID;
v = new PSVariable(SpecialVariables.ShellId, shellId,
ScopedItemOptions.Constant | ScopedItemOptions.AllScope,
RunspaceInit.MshShellIdDescription);
@@ -366,18 +365,10 @@ namespace System.Management.Automation
// $PSHOME
// This depends on the shellId. If we cannot read the application base
// registry key, set the variable to empty string
string applicationBase = string.Empty;
try
{
applicationBase = Utils.GetApplicationBase(shellId);
}
catch (SecurityException)
{
}
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);
}
@@ -477,6 +477,7 @@ namespace System.Management.Automation
internal static readonly string[] ManifestModuleVersionPropertyName = new[] { "ModuleVersion" };
internal static readonly string[] ManifestGuidPropertyName = new[] { "GUID" };
internal static readonly string[] FastModuleManifestAnalysisPropertyNames = new[] { "AliasesToExport", "CmdletsToExport", "FunctionsToExport", "NestedModules", "RootModule", "ModuleToProcess", "ModuleVersion" };
internal static readonly string[] ManifestPrivateDataPropertyName = new[] { "PrivateData" };
internal static Hashtable GetModuleManifestProperties(string psDataFilePath, string[] keys)
{