diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/EnableDisableExperimentalFeatureCommand.cs b/src/System.Management.Automation/engine/ExperimentalFeature/EnableDisableExperimentalFeatureCommand.cs new file mode 100644 index 0000000000..2c9763dcdc --- /dev/null +++ b/src/System.Management.Automation/engine/ExperimentalFeature/EnableDisableExperimentalFeatureCommand.cs @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Configuration; +using System.Management.Automation.Internal; +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Base class for Enable/Disable-ExperimentalFeature cmdlet. + /// + public class EnableDisableExperimentalFeatureCommandBase : PSCmdlet + { + /// + /// Gets or sets the feature names. + /// + [Parameter(ValueFromPipelineByPropertyName = true, Position = 0, Mandatory = true)] + [ArgumentCompleter(typeof(ExperimentalFeatureNameCompleter))] + public string[] Name { get; set; } + + /// + /// Gets or sets the scope of persistence of updating the PowerShell configuration json. + /// + [Parameter] + public ConfigScope Scope { get; set; } = ConfigScope.CurrentUser; + } + + /// + /// Implements Enable-ExperimentalFeature cmdlet. + /// + [Cmdlet(VerbsLifecycle.Enable, "ExperimentalFeature", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2046964")] + public class EnableExperimentalFeatureCommand : EnableDisableExperimentalFeatureCommandBase + { + /// + /// ProcessRecord method of this cmdlet. + /// + protected override void ProcessRecord() + { + ExperimentalFeatureConfigHelper.UpdateConfig(this, Name, Scope, enable: true); + } + } + + /// + /// Implements Enable-ExperimentalFeature cmdlet. + /// + [Cmdlet(VerbsLifecycle.Disable, "ExperimentalFeature", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2046963")] + public class DisableExperimentalFeatureCommand : EnableDisableExperimentalFeatureCommandBase + { + /// + /// ProcessRecord method of this cmdlet. + /// + protected override void ProcessRecord() + { + ExperimentalFeatureConfigHelper.UpdateConfig(this, Name, Scope, enable: false); + } + } + + internal class ExperimentalFeatureConfigHelper + { + internal static void UpdateConfig(PSCmdlet cmdlet, string[] name, ConfigScope scope, bool enable) + { + IEnumerable namePatterns = SessionStateUtilities.CreateWildcardsFromStrings(name, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); + GetExperimentalFeatureCommand getExperimentalFeatureCommand = new GetExperimentalFeatureCommand(); + getExperimentalFeatureCommand.Context = cmdlet.Context; + bool foundFeature = false; + foreach (ExperimentalFeature feature in getExperimentalFeatureCommand.GetAvailableExperimentalFeatures(namePatterns)) + { + foundFeature = true; + if (!cmdlet.ShouldProcess(feature.Name)) + { + return; + } + + PowerShellConfig.Instance.SetExperimentalFeatures(scope, feature.Name, enable); + } + + if (!foundFeature) + { + string errMsg = string.Format(CultureInfo.InvariantCulture, ExperimentalFeatureStrings.ExperimentalFeatureNameNotFound, name); + cmdlet.WriteError(new ErrorRecord(new ItemNotFoundException(errMsg), "ItemNotFoundException", ErrorCategory.ObjectNotFound, name)); + return; + } + + cmdlet.WriteWarning(ExperimentalFeatureStrings.ExperimentalFeaturePending); + } + } + + /// + /// Provides argument completion for ExperimentalFeature names. + /// + public class ExperimentalFeatureNameCompleter : IArgumentCompleter + { + /// + /// Returns completion results for experimental feature names used as arguments to experimental feature cmdlets. + /// + /// The command name. + /// The parameter name. + /// The word to complete. + /// The command AST. + /// The fake bound parameters. + /// List of Completion Results. + public IEnumerable CompleteArgument(string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) + { + if (fakeBoundParameters == null) + { + throw PSTraceSource.NewArgumentNullException(nameof(fakeBoundParameters)); + } + + var commandInfo = new CmdletInfo("Get-ExperimentalFeature", typeof(GetExperimentalFeatureCommand)); + var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace) + .AddCommand(commandInfo) + .AddParameter("Name", wordToComplete + "*"); + + HashSet names = new HashSet(); + var results = ps.Invoke(); + foreach (var result in results) + { + names.Add(result.Name); + } + + return names.OrderBy(name => name).Select(name => new CompletionResult(name, name, CompletionResultType.Text, name)); + } + } +} diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs b/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs index 752742ffb0..07bcf02838 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs @@ -20,15 +20,10 @@ namespace Microsoft.PowerShell.Commands /// Get and set the feature names. /// [Parameter(ValueFromPipeline = true, Position = 0)] + [ArgumentCompleter(typeof(ExperimentalFeatureNameCompleter))] [ValidateNotNullOrEmpty] public string[] Name { get; set; } - /// - /// Get and set the switch flag to search module paths to find all available experimental features. - /// - [Parameter] - public SwitchParameter ListAvailable { get; set; } - /// /// ProcessRecord method of this cmdlet. /// @@ -37,19 +32,9 @@ namespace Microsoft.PowerShell.Commands const WildcardOptions wildcardOptions = WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant; IEnumerable namePatterns = SessionStateUtilities.CreateWildcardsFromStrings(Name, wildcardOptions); - if (ListAvailable) + foreach (ExperimentalFeature feature in GetAvailableExperimentalFeatures(namePatterns).OrderBy(GetSortingString)) { - 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); - } + WriteObject(feature); } } @@ -67,61 +52,10 @@ namespace Microsoft.PowerShell.Commands : (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)) - { - foreach (var feature in ModuleIntrinsics.GetExperimentalFeature(moduleFile)) - { - 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) + internal IEnumerable GetAvailableExperimentalFeatures(IEnumerable namePatterns) { foreach (ExperimentalFeature feature in ExperimentalFeature.EngineExperimentalFeatures) { diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 6c83d545b5..2b745bbe78 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -5103,76 +5103,78 @@ end { cmdlets = new Dictionary(StringComparer.OrdinalIgnoreCase) { - {"Add-History", new SessionStateCmdletEntry("Add-History", typeof(AddHistoryCommand), helpFile) }, - {"Clear-History", new SessionStateCmdletEntry("Clear-History", typeof(ClearHistoryCommand), helpFile) }, - {"Debug-Job", new SessionStateCmdletEntry("Debug-Job", typeof(DebugJobCommand), helpFile) }, + { "Add-History", new SessionStateCmdletEntry("Add-History", typeof(AddHistoryCommand), helpFile) }, + { "Clear-History", new SessionStateCmdletEntry("Clear-History", typeof(ClearHistoryCommand), helpFile) }, + { "Debug-Job", new SessionStateCmdletEntry("Debug-Job", typeof(DebugJobCommand), helpFile) }, #if !UNIX - {"Disable-PSRemoting", new SessionStateCmdletEntry("Disable-PSRemoting", typeof(DisablePSRemotingCommand), helpFile) }, - {"Enable-PSRemoting", new SessionStateCmdletEntry("Enable-PSRemoting", typeof(EnablePSRemotingCommand), helpFile) }, - {"Disable-PSSessionConfiguration", new SessionStateCmdletEntry("Disable-PSSessionConfiguration", typeof(DisablePSSessionConfigurationCommand), helpFile) }, - {"Enable-PSSessionConfiguration", new SessionStateCmdletEntry("Enable-PSSessionConfiguration", typeof(EnablePSSessionConfigurationCommand), helpFile) }, - {"Get-PSSessionCapability", new SessionStateCmdletEntry("Get-PSSessionCapability", typeof(GetPSSessionCapabilityCommand), helpFile) }, - {"Get-PSSessionConfiguration", new SessionStateCmdletEntry("Get-PSSessionConfiguration", typeof(GetPSSessionConfigurationCommand), helpFile) }, - {"New-PSSessionConfigurationFile", new SessionStateCmdletEntry("New-PSSessionConfigurationFile", typeof(NewPSSessionConfigurationFileCommand), helpFile) }, - {"Receive-PSSession", new SessionStateCmdletEntry("Receive-PSSession", typeof(ReceivePSSessionCommand), helpFile) }, - {"Register-PSSessionConfiguration", new SessionStateCmdletEntry("Register-PSSessionConfiguration", typeof(RegisterPSSessionConfigurationCommand), helpFile) }, - {"Unregister-PSSessionConfiguration", new SessionStateCmdletEntry("Unregister-PSSessionConfiguration", typeof(UnregisterPSSessionConfigurationCommand), helpFile) }, - {"Set-PSSessionConfiguration", new SessionStateCmdletEntry("Set-PSSessionConfiguration", typeof(SetPSSessionConfigurationCommand), helpFile) }, - {"Test-PSSessionConfigurationFile", new SessionStateCmdletEntry("Test-PSSessionConfigurationFile", typeof(TestPSSessionConfigurationFileCommand), helpFile) }, - {"Connect-PSSession", new SessionStateCmdletEntry("Connect-PSSession", typeof(ConnectPSSessionCommand), helpFile) }, - {"Disconnect-PSSession", new SessionStateCmdletEntry("Disconnect-PSSession", typeof(DisconnectPSSessionCommand), helpFile) }, + { "Disable-PSRemoting", new SessionStateCmdletEntry("Disable-PSRemoting", typeof(DisablePSRemotingCommand), helpFile) }, + { "Enable-PSRemoting", new SessionStateCmdletEntry("Enable-PSRemoting", typeof(EnablePSRemotingCommand), helpFile) }, + { "Disable-PSSessionConfiguration", new SessionStateCmdletEntry("Disable-PSSessionConfiguration", typeof(DisablePSSessionConfigurationCommand), helpFile) }, + { "Enable-PSSessionConfiguration", new SessionStateCmdletEntry("Enable-PSSessionConfiguration", typeof(EnablePSSessionConfigurationCommand), helpFile) }, + { "Get-PSSessionCapability", new SessionStateCmdletEntry("Get-PSSessionCapability", typeof(GetPSSessionCapabilityCommand), helpFile) }, + { "Get-PSSessionConfiguration", new SessionStateCmdletEntry("Get-PSSessionConfiguration", typeof(GetPSSessionConfigurationCommand), helpFile) }, + { "New-PSSessionConfigurationFile", new SessionStateCmdletEntry("New-PSSessionConfigurationFile", typeof(NewPSSessionConfigurationFileCommand), helpFile) }, + { "Receive-PSSession", new SessionStateCmdletEntry("Receive-PSSession", typeof(ReceivePSSessionCommand), helpFile) }, + { "Register-PSSessionConfiguration", new SessionStateCmdletEntry("Register-PSSessionConfiguration", typeof(RegisterPSSessionConfigurationCommand), helpFile) }, + { "Unregister-PSSessionConfiguration", new SessionStateCmdletEntry("Unregister-PSSessionConfiguration", typeof(UnregisterPSSessionConfigurationCommand), helpFile) }, + { "Set-PSSessionConfiguration", new SessionStateCmdletEntry("Set-PSSessionConfiguration", typeof(SetPSSessionConfigurationCommand), helpFile) }, + { "Test-PSSessionConfigurationFile", new SessionStateCmdletEntry("Test-PSSessionConfigurationFile", typeof(TestPSSessionConfigurationFileCommand), helpFile) }, + { "Connect-PSSession", new SessionStateCmdletEntry("Connect-PSSession", typeof(ConnectPSSessionCommand), helpFile) }, + { "Disconnect-PSSession", new SessionStateCmdletEntry("Disconnect-PSSession", typeof(DisconnectPSSessionCommand), helpFile) }, #endif - {"Enter-PSHostProcess", new SessionStateCmdletEntry("Enter-PSHostProcess", typeof(EnterPSHostProcessCommand), helpFile) }, - {"Enter-PSSession", new SessionStateCmdletEntry("Enter-PSSession", typeof(EnterPSSessionCommand), helpFile) }, - {"Exit-PSHostProcess", new SessionStateCmdletEntry("Exit-PSHostProcess", typeof(ExitPSHostProcessCommand), helpFile) }, - {"Exit-PSSession", new SessionStateCmdletEntry("Exit-PSSession", typeof(ExitPSSessionCommand), helpFile) }, - {"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) }, - {"Get-Module", new SessionStateCmdletEntry("Get-Module", typeof(GetModuleCommand), helpFile) }, - {"Get-PSHostProcessInfo", new SessionStateCmdletEntry("Get-PSHostProcessInfo", typeof(GetPSHostProcessInfoCommand), helpFile) }, - {"Get-PSSession", new SessionStateCmdletEntry("Get-PSSession", typeof(GetPSSessionCommand), helpFile) }, - {"Import-Module", new SessionStateCmdletEntry("Import-Module", typeof(ImportModuleCommand), helpFile) }, - {"Invoke-Command", new SessionStateCmdletEntry("Invoke-Command", typeof(InvokeCommandCommand), helpFile) }, - {"Invoke-History", new SessionStateCmdletEntry("Invoke-History", typeof(InvokeHistoryCommand), helpFile) }, - {"New-Module", new SessionStateCmdletEntry("New-Module", typeof(NewModuleCommand), helpFile) }, - {"New-ModuleManifest", new SessionStateCmdletEntry("New-ModuleManifest", typeof(NewModuleManifestCommand), helpFile) }, - {"New-PSRoleCapabilityFile", new SessionStateCmdletEntry("New-PSRoleCapabilityFile", typeof(NewPSRoleCapabilityFileCommand), helpFile) }, - {"New-PSSession", new SessionStateCmdletEntry("New-PSSession", typeof(NewPSSessionCommand), helpFile) }, - {"New-PSSessionOption", new SessionStateCmdletEntry("New-PSSessionOption", typeof(NewPSSessionOptionCommand), helpFile) }, - {"New-PSTransportOption", new SessionStateCmdletEntry("New-PSTransportOption", typeof(NewPSTransportOptionCommand), helpFile) }, - {"Out-Default", new SessionStateCmdletEntry("Out-Default", typeof(OutDefaultCommand), helpFile) }, - {"Out-Host", new SessionStateCmdletEntry("Out-Host", typeof(OutHostCommand), helpFile) }, - {"Out-Null", new SessionStateCmdletEntry("Out-Null", typeof(OutNullCommand), helpFile) }, - {"Receive-Job", new SessionStateCmdletEntry("Receive-Job", typeof(ReceiveJobCommand), helpFile) }, - {"Register-ArgumentCompleter", new SessionStateCmdletEntry("Register-ArgumentCompleter", typeof(RegisterArgumentCompleterCommand), helpFile) }, - {"Remove-Job", new SessionStateCmdletEntry("Remove-Job", typeof(RemoveJobCommand), helpFile) }, - {"Remove-Module", new SessionStateCmdletEntry("Remove-Module", typeof(RemoveModuleCommand), helpFile) }, - {"Remove-PSSession", new SessionStateCmdletEntry("Remove-PSSession", typeof(RemovePSSessionCommand), helpFile) }, - {"Save-Help", new SessionStateCmdletEntry("Save-Help", typeof(SaveHelpCommand), helpFile) }, - {"Set-PSDebug", new SessionStateCmdletEntry("Set-PSDebug", typeof(SetPSDebugCommand), helpFile) }, - {"Set-StrictMode", new SessionStateCmdletEntry("Set-StrictMode", typeof(SetStrictModeCommand), helpFile) }, - {"Start-Job", new SessionStateCmdletEntry("Start-Job", typeof(StartJobCommand), helpFile) }, - {"Stop-Job", new SessionStateCmdletEntry("Stop-Job", typeof(StopJobCommand), helpFile) }, - {"Test-ModuleManifest", new SessionStateCmdletEntry("Test-ModuleManifest", typeof(TestModuleManifestCommand), helpFile) }, - {"Update-Help", new SessionStateCmdletEntry("Update-Help", typeof(UpdateHelpCommand), helpFile) }, - {"Wait-Job", new SessionStateCmdletEntry("Wait-Job", typeof(WaitJobCommand), helpFile) }, - {"Where-Object", new SessionStateCmdletEntry("Where-Object", typeof(WhereObjectCommand), helpFile) }, + { "Disable-ExperimentalFeature", new SessionStateCmdletEntry("Disable-ExperimentalFeature", typeof(DisableExperimentalFeatureCommand), helpFile) }, + { "Enable-ExperimentalFeature", new SessionStateCmdletEntry("Enable-ExperimentalFeature", typeof(EnableExperimentalFeatureCommand), helpFile) }, + { "Enter-PSHostProcess", new SessionStateCmdletEntry("Enter-PSHostProcess", typeof(EnterPSHostProcessCommand), helpFile) }, + { "Enter-PSSession", new SessionStateCmdletEntry("Enter-PSSession", typeof(EnterPSSessionCommand), helpFile) }, + { "Exit-PSHostProcess", new SessionStateCmdletEntry("Exit-PSHostProcess", typeof(ExitPSHostProcessCommand), helpFile) }, + { "Exit-PSSession", new SessionStateCmdletEntry("Exit-PSSession", typeof(ExitPSSessionCommand), helpFile) }, + { "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) }, + { "Get-Module", new SessionStateCmdletEntry("Get-Module", typeof(GetModuleCommand), helpFile) }, + { "Get-PSHostProcessInfo", new SessionStateCmdletEntry("Get-PSHostProcessInfo", typeof(GetPSHostProcessInfoCommand), helpFile) }, + { "Get-PSSession", new SessionStateCmdletEntry("Get-PSSession", typeof(GetPSSessionCommand), helpFile) }, + { "Import-Module", new SessionStateCmdletEntry("Import-Module", typeof(ImportModuleCommand), helpFile) }, + { "Invoke-Command", new SessionStateCmdletEntry("Invoke-Command", typeof(InvokeCommandCommand), helpFile) }, + { "Invoke-History", new SessionStateCmdletEntry("Invoke-History", typeof(InvokeHistoryCommand), helpFile) }, + { "New-Module", new SessionStateCmdletEntry("New-Module", typeof(NewModuleCommand), helpFile) }, + { "New-ModuleManifest", new SessionStateCmdletEntry("New-ModuleManifest", typeof(NewModuleManifestCommand), helpFile) }, + { "New-PSRoleCapabilityFile", new SessionStateCmdletEntry("New-PSRoleCapabilityFile", typeof(NewPSRoleCapabilityFileCommand), helpFile) }, + { "New-PSSession", new SessionStateCmdletEntry("New-PSSession", typeof(NewPSSessionCommand), helpFile) }, + { "New-PSSessionOption", new SessionStateCmdletEntry("New-PSSessionOption", typeof(NewPSSessionOptionCommand), helpFile) }, + { "New-PSTransportOption", new SessionStateCmdletEntry("New-PSTransportOption", typeof(NewPSTransportOptionCommand), helpFile) }, + { "Out-Default", new SessionStateCmdletEntry("Out-Default", typeof(OutDefaultCommand), helpFile) }, + { "Out-Host", new SessionStateCmdletEntry("Out-Host", typeof(OutHostCommand), helpFile) }, + { "Out-Null", new SessionStateCmdletEntry("Out-Null", typeof(OutNullCommand), helpFile) }, + { "Receive-Job", new SessionStateCmdletEntry("Receive-Job", typeof(ReceiveJobCommand), helpFile) }, + { "Register-ArgumentCompleter", new SessionStateCmdletEntry("Register-ArgumentCompleter", typeof(RegisterArgumentCompleterCommand), helpFile) }, + { "Remove-Job", new SessionStateCmdletEntry("Remove-Job", typeof(RemoveJobCommand), helpFile) }, + { "Remove-Module", new SessionStateCmdletEntry("Remove-Module", typeof(RemoveModuleCommand), helpFile) }, + { "Remove-PSSession", new SessionStateCmdletEntry("Remove-PSSession", typeof(RemovePSSessionCommand), helpFile) }, + { "Save-Help", new SessionStateCmdletEntry("Save-Help", typeof(SaveHelpCommand), helpFile) }, + { "Set-PSDebug", new SessionStateCmdletEntry("Set-PSDebug", typeof(SetPSDebugCommand), helpFile) }, + { "Set-StrictMode", new SessionStateCmdletEntry("Set-StrictMode", typeof(SetStrictModeCommand), helpFile) }, + { "Start-Job", new SessionStateCmdletEntry("Start-Job", typeof(StartJobCommand), helpFile) }, + { "Stop-Job", new SessionStateCmdletEntry("Stop-Job", typeof(StopJobCommand), helpFile) }, + { "Test-ModuleManifest", new SessionStateCmdletEntry("Test-ModuleManifest", typeof(TestModuleManifestCommand), helpFile) }, + { "Update-Help", new SessionStateCmdletEntry("Update-Help", typeof(UpdateHelpCommand), helpFile) }, + { "Wait-Job", new SessionStateCmdletEntry("Wait-Job", typeof(WaitJobCommand), helpFile) }, + { "Where-Object", new SessionStateCmdletEntry("Where-Object", typeof(WhereObjectCommand), helpFile) }, #if !CORECLR - {"Add-PSSnapin", new SessionStateCmdletEntry("Add-PSSnapin", typeof(AddPSSnapinCommand), helpFile) }, - {"Export-Console", new SessionStateCmdletEntry("Export-Console", typeof(ExportConsoleCommand), helpFile) }, - {"Get-PSSnapin", new SessionStateCmdletEntry("Get-PSSnapin", typeof(GetPSSnapinCommand), helpFile) }, - {"Remove-PSSnapin", new SessionStateCmdletEntry("Remove-PSSnapin", typeof(RemovePSSnapinCommand), helpFile) }, - {"Resume-Job", new SessionStateCmdletEntry("Resume-Job", typeof(ResumeJobCommand), helpFile) }, - {"Suspend-Job", new SessionStateCmdletEntry("Suspend-Job", typeof(SuspendJobCommand), helpFile) }, + { "Add-PSSnapin", new SessionStateCmdletEntry("Add-PSSnapin", typeof(AddPSSnapinCommand), helpFile) }, + { "Export-Console", new SessionStateCmdletEntry("Export-Console", typeof(ExportConsoleCommand), helpFile) }, + { "Get-PSSnapin", new SessionStateCmdletEntry("Get-PSSnapin", typeof(GetPSSnapinCommand), helpFile) }, + { "Remove-PSSnapin", new SessionStateCmdletEntry("Remove-PSSnapin", typeof(RemovePSSnapinCommand), helpFile) }, + { "Resume-Job", new SessionStateCmdletEntry("Resume-Job", typeof(ResumeJobCommand), helpFile) }, + { "Suspend-Job", new SessionStateCmdletEntry("Suspend-Job", typeof(SuspendJobCommand), helpFile) }, #endif // Not exported, but are added via reflection so added here as well, though maybe they shouldn't be - {"Out-LineOutput", new SessionStateCmdletEntry("Out-LineOutput", typeof(OutLineOutputCommand), helpFile) }, - {"Format-Default", new SessionStateCmdletEntry("Format-Default", typeof(FormatDefaultCommand), helpFile) }, + { "Out-LineOutput", new SessionStateCmdletEntry("Out-LineOutput", typeof(OutLineOutputCommand), helpFile) }, + { "Format-Default", new SessionStateCmdletEntry("Format-Default", typeof(FormatDefaultCommand), helpFile) }, }; foreach (var val in cmdlets.Values) { diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs index d9985110ef..76e4439a51 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -1286,7 +1286,7 @@ namespace System.Management.Automation // If the Windows PowerShell Module path is already present, insert the system module path // ($PSHOME/Modules) before it. insertIndex = PathContainsSubstring(currentProcessModulePath, windowsPowerShellModulePath); -#endif +#endif currentProcessModulePath = AddToPath(currentProcessModulePath, systemModulePathToUse, insertIndex); } @@ -1295,7 +1295,7 @@ namespace System.Management.Automation // index of $PSHome\Modules in currentProcessModulePath int indexOfPSHomeModulePath = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); - + // if $PSHome\Modules not found (psHomePosition == -1) - append location to the end; // if $PSHome\Modules IS found (psHomePosition >= 0) - insert location before $PSHome\Modules currentProcessModulePath = AddToPath(currentProcessModulePath, sharedModulePath, indexOfPSHomeModulePath); @@ -1322,10 +1322,10 @@ namespace System.Management.Automation private static string SetModulePath() { string currentModulePath = GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Process); - string systemWideModulePath = PowerShellConfig.Instance.GetModulePath(ConfigScope.SystemWide); + string allUsersModulePath = PowerShellConfig.Instance.GetModulePath(ConfigScope.AllUsers); string personalModulePath = PowerShellConfig.Instance.GetModulePath(ConfigScope.CurrentUser); - string newModulePathString = GetModulePath(currentModulePath, systemWideModulePath, personalModulePath); + string newModulePathString = GetModulePath(currentModulePath, allUsersModulePath, personalModulePath); if (!string.IsNullOrEmpty(newModulePathString)) { diff --git a/src/System.Management.Automation/engine/PSConfiguration.cs b/src/System.Management.Automation/engine/PSConfiguration.cs index 643d8d08a8..5487028d9a 100644 --- a/src/System.Management.Automation/engine/PSConfiguration.cs +++ b/src/System.Management.Automation/engine/PSConfiguration.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; @@ -11,12 +12,19 @@ using System.Management.Automation.Internal; namespace System.Management.Automation.Configuration { - internal enum ConfigScope + /// + /// The scope of the configuration file. + /// + public enum ConfigScope { - // SystemWide configuration applies to all users. - SystemWide = 0, + /// + /// AllUsers configuration applies to all users. + /// + AllUsers = 0, - // CurrentUser configuration applies to the current user. + /// + /// CurrentUser configuration applies to the current user. + /// CurrentUser = 1 } @@ -140,15 +148,6 @@ namespace System.Management.Automation.Configuration internal void SetExecutionPolicy(ConfigScope scope, string shellId, string executionPolicy) { - // Defaults to system wide. - if (ConfigScope.CurrentUser == scope) - { - // Exceptions are not caught so that they will propagate to the - // host for display to the user. - // CreateDirectory will succeed if the directory already exists - // so there is no reason to check Directory.Exists(). - Directory.CreateDirectory(perUserConfigDirectory); - } string valueName = string.Concat(shellId, ":", "ExecutionPolicy"); WriteValueToFile(scope, valueName, executionPolicy); } @@ -165,12 +164,12 @@ namespace System.Management.Automation.Configuration /// Whether console prompting should happen. If the value cannot be read it defaults to false. internal bool GetConsolePrompting() { - return ReadValueFromFile(ConfigScope.SystemWide, "ConsolePrompting"); + return ReadValueFromFile(ConfigScope.AllUsers, "ConsolePrompting"); } internal void SetConsolePrompting(bool shouldPrompt) { - WriteValueToFile(ConfigScope.SystemWide, "ConsolePrompting", shouldPrompt); + WriteValueToFile(ConfigScope.AllUsers, "ConsolePrompting", shouldPrompt); } /// @@ -185,12 +184,12 @@ namespace System.Management.Automation.Configuration /// Boolean indicating whether Update-Help should prompt. If the value cannot be read, it defaults to false. internal bool GetDisablePromptToUpdateHelp() { - return ReadValueFromFile(ConfigScope.SystemWide, "DisablePromptToUpdateHelp"); + return ReadValueFromFile(ConfigScope.AllUsers, "DisablePromptToUpdateHelp"); } internal void SetDisablePromptToUpdateHelp(bool prompt) { - WriteValueToFile(ConfigScope.SystemWide, "DisablePromptToUpdateHelp", prompt); + WriteValueToFile(ConfigScope.AllUsers, "DisablePromptToUpdateHelp", prompt); } /// @@ -198,7 +197,40 @@ namespace System.Management.Automation.Configuration /// internal string[] GetExperimentalFeatures() { - return ReadValueFromFile(ConfigScope.SystemWide, "ExperimentalFeatures", Utils.EmptyArray()); + string[] features = Array.Empty(); + if (File.Exists(perUserConfigFile)) + { + features = ReadValueFromFile(ConfigScope.CurrentUser, "ExperimentalFeatures", Array.Empty()); + } + + if (features.Length == 0) + { + features = ReadValueFromFile(ConfigScope.AllUsers, "ExperimentalFeatures", Array.Empty()); + } + + return features; + } + + /// + /// Set the enabled list of experimental features in the config file. + /// + /// The ConfigScope of the configuration file to update. + /// The name of the experimental feature to change in the configuration. + /// If true, add to configuration; otherwise, remove from configuration. + internal void SetExperimentalFeatures(ConfigScope scope, string featureName, bool setEnabled) + { + var features = new List(GetExperimentalFeatures()); + bool containsFeature = features.Contains(featureName); + if (setEnabled && !containsFeature) + { + features.Add(featureName); + WriteValueToFile(scope, "ExperimentalFeatures", features.ToArray()); + } + else if (!setEnabled && containsFeature) + { + features.Remove(featureName); + WriteValueToFile(scope, "ExperimentalFeatures", features.ToArray()); + } } /// @@ -218,7 +250,7 @@ namespace System.Management.Automation.Configuration /// internal string GetSysLogIdentity() { - string identity = ReadValueFromFile(ConfigScope.SystemWide, "LogIdentity"); + string identity = ReadValueFromFile(ConfigScope.AllUsers, "LogIdentity"); if (string.IsNullOrEmpty(identity) || identity.Equals(LogDefaultValue, StringComparison.OrdinalIgnoreCase)) @@ -236,7 +268,7 @@ namespace System.Management.Automation.Configuration /// internal PSLevel GetLogLevel() { - string levelName = ReadValueFromFile(ConfigScope.SystemWide, "LogLevel"); + string levelName = ReadValueFromFile(ConfigScope.AllUsers, "LogLevel"); PSLevel level; if (string.IsNullOrEmpty(levelName) || @@ -266,7 +298,7 @@ namespace System.Management.Automation.Configuration /// internal PSChannel GetLogChannels() { - string values = ReadValueFromFile(ConfigScope.SystemWide, "LogChannels"); + string values = ReadValueFromFile(ConfigScope.AllUsers, "LogChannels"); PSChannel result = 0; if (!string.IsNullOrEmpty(values)) @@ -305,7 +337,7 @@ namespace System.Management.Automation.Configuration /// internal PSKeyword GetLogKeywords() { - string values = ReadValueFromFile(ConfigScope.SystemWide, "LogKeywords"); + string values = ReadValueFromFile(ConfigScope.AllUsers, "LogKeywords"); PSKeyword result = 0; if (!string.IsNullOrEmpty(values)) @@ -516,6 +548,16 @@ namespace System.Management.Automation.Configuration /// The value to write. private void WriteValueToFile(ConfigScope scope, string key, T value) { + // Defaults to system wide. + if (ConfigScope.CurrentUser == scope) + { + // Exceptions are not caught so that they will propagate to the + // host for display to the user. + // CreateDirectory will succeed if the directory already exists + // so there is no reason to check Directory.Exists(). + Directory.CreateDirectory(perUserConfigDirectory); + } + UpdateValueInFile(scope, key, value, true); } diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index c17782be1a..848a8b7d4e 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -488,10 +488,10 @@ namespace System.Management.Automation /// internal static string ModuleDirectory = Path.Combine(ProductNameForDirectory, "Modules"); - internal readonly static ConfigScope[] SystemWideOnlyConfig = new[] { ConfigScope.SystemWide }; + internal readonly static ConfigScope[] SystemWideOnlyConfig = new[] { ConfigScope.AllUsers }; internal readonly static ConfigScope[] CurrentUserOnlyConfig = new[] { ConfigScope.CurrentUser }; - internal readonly static ConfigScope[] SystemWideThenCurrentUserConfig = new[] { ConfigScope.SystemWide, ConfigScope.CurrentUser }; - internal readonly static ConfigScope[] CurrentUserThenSystemWideConfig = new[] { ConfigScope.CurrentUser, ConfigScope.SystemWide }; + internal readonly static ConfigScope[] SystemWideThenCurrentUserConfig = new[] { ConfigScope.AllUsers, ConfigScope.CurrentUser }; + internal readonly static ConfigScope[] CurrentUserThenSystemWideConfig = new[] { ConfigScope.CurrentUser, ConfigScope.AllUsers }; internal static T GetPolicySetting(ConfigScope[] preferenceOrder) where T : PolicyBase, new() { @@ -572,7 +572,7 @@ namespace System.Management.Automation { Type tType = typeof(T); // SystemWide scope means 'LocalMachine' root key when query from registry - RegistryKey rootKey = (scope == ConfigScope.SystemWide) ? Registry.LocalMachine : Registry.CurrentUser; + RegistryKey rootKey = (scope == ConfigScope.AllUsers) ? Registry.LocalMachine : Registry.CurrentUser; GroupPolicyKeys.TryGetValue(tType.Name, out string gpoKeyPath); Diagnostics.Assert(gpoKeyPath != null, StringUtil.Format("The GPO registry key path should be pre-defined for {0}", tType.Name)); @@ -1799,7 +1799,7 @@ namespace System.Management.Automation.Internal } /// - /// Test hook used to test implicit remoting batching. A local runspace must be provided that has imported a + /// Test hook used to test implicit remoting batching. A local runspace must be provided that has imported a /// remote session, i.e., has run the Import-PSSession cmdlet. This hook will return true if the provided commandPipeline /// is successfully batched and run in the remote session, and false if it is rejected for batching. /// diff --git a/src/System.Management.Automation/resources/ExperimentalFeatureStrings.resx b/src/System.Management.Automation/resources/ExperimentalFeatureStrings.resx new file mode 100644 index 0000000000..9823f54d51 --- /dev/null +++ b/src/System.Management.Automation/resources/ExperimentalFeatureStrings.resx @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + No experimental feature was found that matched the name '{0}'. + + + Enabling and disabling experimental features do not take effect until next start of PowerShell. + + diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index dd89eb4ee6..7deb1bcfd1 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -184,11 +184,11 @@ namespace System.Management.Automation.Internal // They want to remove it if (policy == ExecutionPolicy.Undefined) { - PowerShellConfig.Instance.RemoveExecutionPolicy(ConfigScope.SystemWide, shellId); + PowerShellConfig.Instance.RemoveExecutionPolicy(ConfigScope.AllUsers, shellId); } else { - PowerShellConfig.Instance.SetExecutionPolicy(ConfigScope.SystemWide, shellId, executionPolicy); + PowerShellConfig.Instance.SetExecutionPolicy(ConfigScope.AllUsers, shellId, executionPolicy); } break; } @@ -466,7 +466,7 @@ namespace System.Management.Automation.Internal // 2: Look up the system-wide preference case ExecutionPolicyScope.LocalMachine: - return PowerShellConfig.Instance.GetExecutionPolicy(ConfigScope.SystemWide, shellId); + return PowerShellConfig.Instance.GetExecutionPolicy(ConfigScope.AllUsers, shellId); } return null; diff --git a/test/csharp/test_PSConfiguration.cs b/test/csharp/test_PSConfiguration.cs index e474ff699a..173dea6810 100644 --- a/test/csharp/test_PSConfiguration.cs +++ b/test/csharp/test_PSConfiguration.cs @@ -348,7 +348,7 @@ namespace PSTests.Sequential public void PowerShellConfig_GetPowerShellPolicies_BothConfigFilesNotEmpty() { fixture.SetupConfigFile1(); - var sysPolicies = PowerShellConfig.Instance.GetPowerShellPolicies(ConfigScope.SystemWide); + var sysPolicies = PowerShellConfig.Instance.GetPowerShellPolicies(ConfigScope.AllUsers); var userPolicies = PowerShellConfig.Instance.GetPowerShellPolicies(ConfigScope.CurrentUser); Assert.NotNull(sysPolicies); @@ -362,7 +362,7 @@ namespace PSTests.Sequential public void PowerShellConfig_GetPowerShellPolicies_EmptyUserConfig() { fixture.SetupConfigFile2(); - var sysPolicies = PowerShellConfig.Instance.GetPowerShellPolicies(ConfigScope.SystemWide); + var sysPolicies = PowerShellConfig.Instance.GetPowerShellPolicies(ConfigScope.AllUsers); var userPolicies = PowerShellConfig.Instance.GetPowerShellPolicies(ConfigScope.CurrentUser); Assert.NotNull(sysPolicies); @@ -375,7 +375,7 @@ namespace PSTests.Sequential public void PowerShellConfig_GetPowerShellPolicies_EmptySystemConfig() { fixture.SetupConfigFile3(); - var sysPolicies = PowerShellConfig.Instance.GetPowerShellPolicies(ConfigScope.SystemWide); + var sysPolicies = PowerShellConfig.Instance.GetPowerShellPolicies(ConfigScope.AllUsers); var userPolicies = PowerShellConfig.Instance.GetPowerShellPolicies(ConfigScope.CurrentUser); Assert.Null(sysPolicies); @@ -388,7 +388,7 @@ namespace PSTests.Sequential public void PowerShellConfig_GetPowerShellPolicies_BothConfigFilesEmpty() { fixture.SetupConfigFile4(); - var sysPolicies = PowerShellConfig.Instance.GetPowerShellPolicies(ConfigScope.SystemWide); + var sysPolicies = PowerShellConfig.Instance.GetPowerShellPolicies(ConfigScope.AllUsers); var userPolicies = PowerShellConfig.Instance.GetPowerShellPolicies(ConfigScope.CurrentUser); Assert.Null(sysPolicies); @@ -399,7 +399,7 @@ namespace PSTests.Sequential public void PowerShellConfig_GetPowerShellPolicies_BothConfigFilesNotExist() { fixture.CleanupConfigFiles(); - var sysPolicies = PowerShellConfig.Instance.GetPowerShellPolicies(ConfigScope.SystemWide); + var sysPolicies = PowerShellConfig.Instance.GetPowerShellPolicies(ConfigScope.AllUsers); var userPolicies = PowerShellConfig.Instance.GetPowerShellPolicies(ConfigScope.CurrentUser); Assert.Null(sysPolicies); @@ -899,4 +899,4 @@ namespace PSTests.Sequential fixture.CompareConsoleSessionConfiguration(consoleSessionConfiguration, null); } } -} \ No newline at end of file +} diff --git a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 index a35281c149..d42a67880e 100644 --- a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 +++ b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 @@ -209,6 +209,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Cmdlet", "Debug-Process", , $($FullCLR -or $CoreWindows -or $CoreUnix) "Cmdlet", "Debug-Runspace", , $($FullCLR -or $CoreWindows -or $CoreUnix) "Cmdlet", "Disable-ComputerRestore", , $($FullCLR ) +"Cmdlet", "Disable-ExperimentalFeature", , $( $CoreWindows -or $CoreUnix) "Cmdlet", "Disable-PSBreakpoint", , $($FullCLR -or $CoreWindows -or $CoreUnix) "Cmdlet", "Disable-PSRemoting", , $($FullCLR -or $CoreWindows ) "Cmdlet", "Disable-PSSessionConfiguration", , $($FullCLR -or $CoreWindows ) @@ -217,6 +218,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Cmdlet", "Disconnect-PSSession", , $($FullCLR -or $CoreWindows ) "Cmdlet", "Disconnect-WSMan", , $($FullCLR -or $CoreWindows ) "Cmdlet", "Enable-ComputerRestore", , $($FullCLR ) +"Cmdlet", "Enable-ExperimentalFeature", , $( $CoreWindows -or $CoreUnix) "Cmdlet", "Enable-PSBreakpoint", , $($FullCLR -or $CoreWindows -or $CoreUnix) "Cmdlet", "Enable-PSRemoting", , $($FullCLR -or $CoreWindows ) "Cmdlet", "Enable-PSSessionConfiguration", , $($FullCLR -or $CoreWindows ) diff --git a/test/powershell/engine/ExperimentalFeature/EnableDisable-ExperimentalFeature.Tests.ps1 b/test/powershell/engine/ExperimentalFeature/EnableDisable-ExperimentalFeature.Tests.ps1 new file mode 100644 index 0000000000..aec3feddf7 --- /dev/null +++ b/test/powershell/engine/ExperimentalFeature/EnableDisable-ExperimentalFeature.Tests.ps1 @@ -0,0 +1,87 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +Describe "Enable-ExperimentalFeature and Disable-ExperimentalFeature tests" -tags "Feature","RequireAdminOnWindows" { + + BeforeAll { + $systemConfigPath = "$PSHOME/powershell.config.json" + if ($IsWindows) { + $userConfigPath = "~/Documents/powershell/powershell.config.json" + } + else { + $userConfigPath = "~/.config/powershell/powershell.config.json" + } + + $systemConfigExists = $false + if (Test-Path $systemConfigPath) { + $systemConfigExists = $true + Move-Item $systemConfigPath "$systemConfigPath.backup" -Force -ErrorAction SilentlyContinue + } + + $userConfigExists = $false + if (Test-Path $userConfigPath) { + $userConfigExists = $true + Move-Item $userConfigPath "$userConfigPath.backup" -Force -ErrorAction SilentlyContinue + } + + $testModulePath = Join-Path -Path $PSScriptRoot -ChildPath "assets" + $originalModulePath = $env:PSModulePath + $env:PSModulePath = $testModulePath + } + + AfterAll { + if ($systemConfigExists) { + Move-Item "$systemConfigPath.backup" $systemConfigPath -Force -ErrorAction SilentlyContinue + } + + if ($userConfigExists) { + Move-Item "$userConfigPath.backup" $userConfigPath -Force -ErrorAction SilentlyContinue + } + + $env:PSModulePath = $originalModulePath + } + + AfterEach { + Remove-Item $systemConfigPath -Force -ErrorAction SilentlyContinue + Remove-Item $userConfigPath -Force -ErrorAction SilentlyContinue + } + + It "Enable-ExperimentalFeature will enable Experimental Feature for scope: " -TestCases @( + @{ scope = "AllUsers" }, + @{ scope = "CurrentUser" } + ) { + param ($scope) + + $feature = pwsh -noprofile -output xml -command Get-ExperimentalFeature ExpTest.FeatureOne + $feature.Enabled | Should -BeFalse -Because "All Experimental Features disabled when no config file" + $feature = pwsh -noprofile -output xml -command Enable-ExperimentalFeature ExpTest.FeatureOne -Scope $scope -WarningAction SilentlyContinue + $feature | Should -BeNullOrEmpty -Because "No object is output to pipeline on success" + $feature = pwsh -noprofile -output xml -command Get-ExperimentalFeature ExpTest.FeatureOne + $feature.Enabled | Should -BeTrue -Because "The experimental feature is now enabled" + } + + It "Disable-ExperimentalFeature will disable Experimental Feature for scope: " -TestCases @( + @{ scope = "AllUsers" ; configPath = $systemConfigPath }, + @{ scope = "CurrentUser"; configPath = $userConfigPath } + ) { + param ($scope, $configPath) + + '{"ExperimentalFeatures":["ExpTest.FeatureOne"]}' > $configPath + $feature = pwsh -noprofile -output xml -command Get-ExperimentalFeature ExpTest.FeatureOne + $feature.Enabled | Should -BeTrue -Because "Test config should enable ExpTest.FeatureOne" + $feature = pwsh -noprofile -output xml -command Disable-ExperimentalFeature ExpTest.FeatureOne -Scope $scope -WarningAction SilentlyContinue + $feature | Should -BeNullOrEmpty -Because "No object is output to pipeline on success" + $feature = pwsh -noprofile -output xml -command Get-ExperimentalFeature ExpTest.FeatureOne + $feature.Enabled | Should -BeFalse -Because "The experimental feature is now disabled" + } + + It " will output warning message" -TestCases @( + @{ cmdlet = "Enable-ExperimentalFeature" }, + @{ cmdlet = "Disable-Experimentalfeature" } + ) { + param ($cmdlet) + + & $cmdlet ExpTest.FeatureOne -WarningVariable warning -WarningAction SilentlyContinue + $warning | Should -Not -BeNullOrEmpty -Because "A warning message is always given indicating restart is required" + } +} diff --git a/test/powershell/engine/ExperimentalFeature/Get-ExperimentalFeature.Tests.ps1 b/test/powershell/engine/ExperimentalFeature/Get-ExperimentalFeature.Tests.ps1 index 4681b0660e..6841079838 100644 --- a/test/powershell/engine/ExperimentalFeature/Get-ExperimentalFeature.Tests.ps1 +++ b/test/powershell/engine/ExperimentalFeature/Get-ExperimentalFeature.Tests.ps1 @@ -1,117 +1,126 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -Describe "Get-ExperimentalFeature basic tests - Feature-Disabled" -tags "CI" { +Describe "Get-ExperimentalFeature Tests" -tags "Feature","RequireAdminOnWindows" { BeforeAll { - $skipTest = $EnabledExperimentalFeatures.Contains('ExpTest.FeatureOne') - - if ($skipTest) { - Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'ExpTest.FeatureOne' to be disabled." -Verbose - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() - $PSDefaultParameterValues["it:skip"] = $true - } else { - Remove-Module -Name ExpTest -Force -ErrorAction SilentlyContinue - $testModulePath = Join-Path -Path $PSScriptRoot -ChildPath "assets" - $testModuleManifestPath = Join-Path -Path $testModulePath "ExpTest" "ExpTest.psd1" - $originalModulePath = $env:PSModulePath - $env:PSModulePath = $testModulePath + $systemConfigPath = "$PSHOME/powershell.config.json" + if ($IsWindows) { + $userConfigPath = "~/Documents/powershell/powershell.config.json" } + else { + $userConfigPath = "~/.config/powershell/powershell.config.json" + } + + $systemConfigExists = $false + if (Test-Path $systemConfigPath) { + $systemConfigExists = $true + Move-Item $systemConfigPath "$systemConfigPath.backup" -Force -ErrorAction SilentlyContinue + } + + $userConfigExists = $false + if (Test-Path $userConfigPath) { + $userConfigExists = $true + Move-Item $userConfigPath "$userConfigPath.backup" -Force -ErrorAction SilentlyContinue + } + + $testModulePath = Join-Path -Path $PSScriptRoot -ChildPath "assets" + $testModuleManifestPath = Join-Path -Path $testModulePath "ExpTest" "ExpTest.psd1" + $originalModulePath = $env:PSModulePath + $env:PSModulePath = $testModulePath } AfterAll { - if ($skipTest) { - $global:PSDefaultParameterValues = $originalDefaultParameterValues - } else { - $env:PSModulePath = $originalModulePath + if ($systemConfigExists) { + Move-Item "$systemConfigPath.backup" $systemConfigPath -Force -ErrorAction SilentlyContinue + } + + if ($userConfigExists) { + Move-Item "$userConfigPath.backup" $userConfigPath -Force -ErrorAction SilentlyContinue + } + + $env:PSModulePath = $originalModulePath + } + + AfterEach { + Remove-Item $systemConfigPath -Force -ErrorAction SilentlyContinue + Remove-Item $userConfigPath -Force -ErrorAction SilentlyContinue + } + + Context "Feature disabled tests" { + + It "'Get-ExperimentalFeature' should return all available features from module path" { + $features = pwsh -noprofile -output xml -command Get-ExperimentalFeature "ExpTest*" + $features | Should -Not -BeNullOrEmpty + $features[0].Name | Should -BeExactly "ExpTest.FeatureOne" + $features[0].Enabled | Should -BeFalse + $features[0].Source | Should -BeExactly $testModuleManifestPath + + $features[1].Name | Should -BeExactly "ExpTest.FeatureTwo" + $features[1].Enabled | Should -BeFalse + $features[1].Source | Should -BeExactly $testModuleManifestPath + } + + It "'Get-ExperimentalFeature' pipeline input" { + $features = pwsh -noprofile -output xml -command { "ExpTest.FeatureOne", "ExpTest.FeatureTwo" | Get-ExperimentalFeature } + $features | Should -Not -BeNullOrEmpty + $features[0].Name | Should -BeExactly "ExpTest.FeatureOne" + $features[0].Enabled | Should -BeFalse + $features[0].Source | Should -BeExactly $testModuleManifestPath + + $features[1].Name | Should -BeExactly "ExpTest.FeatureTwo" + $features[1].Enabled | Should -BeFalse + $features[1].Source | Should -BeExactly $testModuleManifestPath } } - It "'Get-ExperimentalFeature' should only return enabled features" { - $EnabledExperimentalFeatures.Count | Should -Be 0 - Get-ExperimentalFeature | Should -BeNullOrEmpty + Context "Feature enabled tests" { + BeforeEach { + '{"ExperimentalFeatures":["ExpTest.FeatureOne"]}' > $userConfigPath + } + + It "'Get-ExperimentalFeature' should return enabled features 'ExpTest.FeatureOne'" { + pwsh -noprofile -command '$EnabledExperimentalFeatures.Count' | Should -Be 1 + $feature = pwsh -noprofile -output xml -command Get-ExperimentalFeature "ExpTest.FeatureOne" + $feature | Should -Not -BeNullOrEmpty + $feature.Enabled | Should -BeTrue + $feature.Source | Should -BeExactly $testModuleManifestPath + } + + It "'Get-ExperimentalFeature' should return all available features from module path" { + $features = pwsh -noprofile -output xml -command Get-ExperimentalFeature "ExpTest*" + $features | Should -Not -BeNullOrEmpty + $features[0].Name | Should -BeExactly "ExpTest.FeatureOne" + $features[0].Enabled | Should -BeTrue + $features[0].Source | Should -BeExactly $testModuleManifestPath + + $features[1].Name | Should -BeExactly "ExpTest.FeatureTwo" + $features[1].Enabled | Should -BeFalse + $features[1].Source | Should -BeExactly $testModuleManifestPath + } + + It "'Get-ExperimentalFeature' pipeline input" { + $features = pwsh -noprofile -output xml -command { "ExpTest.FeatureOne", "ExpTest.FeatureTwo" | Get-ExperimentalFeature } + $features | Should -Not -BeNullOrEmpty + $features[0].Name | Should -BeExactly "ExpTest.FeatureOne" + $features[0].Enabled | Should -BeTrue + $features[0].Source | Should -BeExactly $testModuleManifestPath + + $features[1].Name | Should -BeExactly "ExpTest.FeatureTwo" + $features[1].Enabled | Should -BeFalse + $features[1].Source | Should -BeExactly $testModuleManifestPath + } } - It "'Get-ExperimentalFeature -ListAvailable' should return all available features from module path" { - $features = Get-ExperimentalFeature "ExpTest*" -ListAvailable - $features | Should -Not -BeNullOrEmpty - $features[0].Name | Should -BeExactly "ExpTest.FeatureOne" - $features[0].Enabled | Should -Be $false - $features[0].Source | Should -BeExactly $testModuleManifestPath + Context "User config takes precedence over system config" { + It "Feature is enabled in user config only" { + '{"ExperimentalFeatures":["ExpTest.FeatureOne"]}' > $userConfigPath + '{"ExperimentalFeatures":["ExpTest.FeatureTwo"]}' > $systemConfigPath - $features[1].Name | Should -BeExactly "ExpTest.FeatureTwo" - $features[1].Enabled | Should -Be $false - $features[1].Source | Should -BeExactly $testModuleManifestPath - } - - It "'Get-ExperimentalFeature -ListAvailable' pipeline input" { - $features = "ExpTest.FeatureOne", "ExpTest.FeatureTwo" | Get-ExperimentalFeature -ListAvailable - $features | Should -Not -BeNullOrEmpty - $features[0].Name | Should -BeExactly "ExpTest.FeatureOne" - $features[0].Enabled | Should -Be $false - $features[0].Source | Should -BeExactly $testModuleManifestPath - - $features[1].Name | Should -BeExactly "ExpTest.FeatureTwo" - $features[1].Enabled | Should -Be $false - $features[1].Source | Should -BeExactly $testModuleManifestPath - } -} - -Describe "Get-ExperimentalFeature basic tests - Feature-Enabled" -tags "CI" { - - BeforeAll { - $skipTest = -not $EnabledExperimentalFeatures.Contains('ExpTest.FeatureOne') - - if ($skipTest) { - Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'ExpTest.FeatureOne' to be enabled." -Verbose - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() - $PSDefaultParameterValues["it:skip"] = $true - } else { - Remove-Module -Name ExpTest -Force -ErrorAction SilentlyContinue - $testModulePath = Join-Path -Path $PSScriptRoot -ChildPath "assets" - $testModuleManifestPath = Join-Path -Path $testModulePath "ExpTest" "ExpTest.psd1" - $originalModulePath = $env:PSModulePath - $env:PSModulePath = $testModulePath + $feature = pwsh -noprofile -output xml -command Get-ExperimentalFeature ExpTest.FeatureOne + $feature.Enabled | Should -BeTrue -Because "FeatureOne is enabled in user config" + $feature = pwsh -noprofile -output xml -command Get-ExperimentalFeature ExpTest.FeatureTwo + $feature.Enabled | Should -BeFalse -Because "System config is not read when user config exists" } - } - - AfterAll { - if ($skipTest) { - $global:PSDefaultParameterValues = $originalDefaultParameterValues - } else { - $env:PSModulePath = $originalModulePath - } - } - - It "'Get-ExperimentalFeature' should return enabled features 'ExpTest.FeatureOne'" { - $EnabledExperimentalFeatures.Count | Should -Be 1 - $feature = Get-ExperimentalFeature "ExpTest.FeatureOne" - $feature | Should -Not -BeNullOrEmpty - $feature.Enabled | Should -Be $true - $feature.Source | Should -BeExactly $testModuleManifestPath - } - - It "'Get-ExperimentalFeature -ListAvailable' should return all available features from module path" { - $features = Get-ExperimentalFeature "ExpTest*" -ListAvailable - $features | Should -Not -BeNullOrEmpty - $features[0].Name | Should -BeExactly "ExpTest.FeatureOne" - $features[0].Enabled | Should -Be $true - $features[0].Source | Should -BeExactly $testModuleManifestPath - - $features[1].Name | Should -BeExactly "ExpTest.FeatureTwo" - $features[1].Enabled | Should -Be $false - $features[1].Source | Should -BeExactly $testModuleManifestPath - } - - It "'Get-ExperimentalFeature -ListAvailable' pipeline input" { - $features = "ExpTest.FeatureOne", "ExpTest.FeatureTwo" | Get-ExperimentalFeature -ListAvailable - $features | Should -Not -BeNullOrEmpty - $features[0].Name | Should -BeExactly "ExpTest.FeatureOne" - $features[0].Enabled | Should -Be $true - $features[0].Source | Should -BeExactly $testModuleManifestPath - - $features[1].Name | Should -BeExactly "ExpTest.FeatureTwo" - $features[1].Enabled | Should -Be $false - $features[1].Source | Should -BeExactly $testModuleManifestPath } } diff --git a/test/powershell/engine/Help/HelpSystem.Tests.ps1 b/test/powershell/engine/Help/HelpSystem.Tests.ps1 index 5c796105f4..10654c8a62 100644 --- a/test/powershell/engine/Help/HelpSystem.Tests.ps1 +++ b/test/powershell/engine/Help/HelpSystem.Tests.ps1 @@ -11,7 +11,9 @@ $script:cmdletsToSkip = @( "Get-PSSessionCapability", "Disable-PSRemoting", # Content not available: Issue # https://github.com/PowerShell/PowerShell-Docs/issues/1790 "Enable-PSRemoting", - "Get-ExperimentalFeature" + "Get-ExperimentalFeature", + "Enable-ExperimentalFeature", + "Disable-ExperimentalFeature" ) function UpdateHelpFromLocalContentPath { diff --git a/test/tools/TestMetadata.json b/test/tools/TestMetadata.json index fbfcbcf45c..c49ab1758c 100644 --- a/test/tools/TestMetadata.json +++ b/test/tools/TestMetadata.json @@ -1,5 +1,5 @@ { "ExperimentalFeatures": { - "ExpTest.FeatureOne": [ "test/powershell/engine/ExperimentalFeature" ] + "ExpTest.FeatureOne": [ "test/powershell/engine/ExperimentalFeature/ExperimentalFeature.Basic.Tests.ps1" ] } }