diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
index 3c6b2060fb..7a76b0075b 100644
--- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
+++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
@@ -286,7 +286,7 @@ namespace System.Management.Automation
///
/// An instance that represents the none-value.
///
- 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;
diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs b/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs
new file mode 100644
index 0000000000..2ab20492d1
--- /dev/null
+++ b/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs
@@ -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
+{
+ ///
+ /// Implements Get-ExperimentalFeature cmdlet.
+ ///
+ [Cmdlet(VerbsCommon.Get, "ExperimentalFeature", HelpUri = "")]
+ public class GetExperimentalFeatureCommand : PSCmdlet
+ {
+ ///
+ /// Specify the feature names.
+ ///
+ [Parameter(ValueFromPipeline = true, Position = 0)]
+ [ValidateNotNullOrEmpty]
+ public string[] Name { get; set; }
+
+ ///
+ /// Search module paths to find all available experimental features.
+ ///
+ [Parameter()]
+ public SwitchParameter ListAvailable { get; set; }
+
+ ///
+ /// ProcessRecord method of this cmdlet.
+ ///
+ protected override void ProcessRecord()
+ {
+ const WildcardOptions wildcardOptions = WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant;
+ IEnumerable 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);
+ }
+ }
+ }
+
+ ///
+ /// Construct the string for sorting experimental feature records.
+ ///
+ ///
+ /// Engine features come before module features.
+ /// Within engine features and more features, features are ordered by name.
+ ///
+ private static string GetSortingString(ExperimentalFeature feature)
+ {
+ if (ExperimentalFeature.EngineSource.Equals(feature.Source))
+ {
+ return "0:" + feature.Name;
+ }
+ else
+ {
+ return "1:" + feature.Name;
+ }
+ }
+
+ ///
+ /// Get enabled experimental features based on the specified name patterns.
+ ///
+ private IEnumerable GetEnabledExperimentalFeatures(IEnumerable namePatterns)
+ {
+ var moduleFeatures = new List();
+ var moduleNames = new HashSet(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(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);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Get available experimental features based on the specified name patterns.
+ ///
+ private IEnumerable GetAvailableExperimentalFeatures(IEnumerable 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;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Get valid module files from module paths.
+ ///
+ private IEnumerable GetValidModuleFiles(HashSet moduleNamesToFind)
+ {
+ var modulePaths = new HashSet(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;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs
index c31eba64b5..90bec19566 100644
--- a/src/System.Management.Automation/engine/InitialSessionState.cs
+++ b/src/System.Management.Automation/engine/InitialSessionState.cs
@@ -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) },
diff --git a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs
index 67aaf6309e..79ec351b28 100644
--- a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs
+++ b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs
@@ -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
{
diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs
index 8451daa80f..99fcdb89eb 100644
--- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs
+++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs
@@ -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();
+ 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();
+ }
+
// 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,
diff --git a/src/System.Management.Automation/engine/SessionState.cs b/src/System.Management.Automation/engine/SessionState.cs
index 516828c743..4f66c42110 100644
--- a/src/System.Management.Automation/engine/SessionState.cs
+++ b/src/System.Management.Automation/engine/SessionState.cs
@@ -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);
}
diff --git a/src/System.Management.Automation/utils/PsUtils.cs b/src/System.Management.Automation/utils/PsUtils.cs
index 3fe10bacba..5734005632 100644
--- a/src/System.Management.Automation/utils/PsUtils.cs
+++ b/src/System.Management.Automation/utils/PsUtils.cs
@@ -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)
{