diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 9313e72e7eb..a260956bcba 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -134,7 +134,12 @@ static ExperimentalFeature() #endif new ExperimentalFeature( name: "PSNullConditionalOperators", - description: "Support the null conditional member access operators in PowerShell language") + description: "Support the null conditional member access operators in PowerShell language"), +#if !UNIX + new ExperimentalFeature( + name: "PSWindowsPowerShellCompatibility", + description: "Load non-PSCore-compartible modules into Windows PowerShell over PS Remoting") +#endif }; EngineExperimentalFeatures = new ReadOnlyCollection(engineFeatures); diff --git a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs index 99943d8faa6..6a88107e7a3 100644 --- a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs +++ b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs @@ -103,7 +103,7 @@ private static ConcurrentDictionary AnalyzeManifestModule( var moduleManifestProperties = PsUtils.GetModuleManifestProperties(modulePath, PsUtils.FastModuleManifestAnalysisPropertyNames); if (moduleManifestProperties != null) { - if (ModuleIsEditionIncompatible(modulePath, moduleManifestProperties)) + if (!ExperimentalFeature.IsEnabled("PSWindowsPowerShellCompatibility") && ModuleIsEditionIncompatible(modulePath, moduleManifestProperties)) { ModuleIntrinsics.Tracer.WriteLine($"Module lies on the Windows System32 legacy module path and is incompatible with current PowerShell edition, skipping module: {modulePath}"); return null; @@ -493,7 +493,7 @@ internal static void CacheModuleExports(PSModuleInfo module, ExecutionContext co // Don't cache incompatible modules on the system32 module path even if loaded with // -SkipEditionCheck, since it will break subsequent sessions. - if (!module.IsConsideredEditionCompatible) + if (!ExperimentalFeature.IsEnabled("PSWindowsPowerShellCompatibility") && !module.IsConsideredEditionCompatible) { ModuleIntrinsics.Tracer.WriteLine($"Module '{module.Name}' not edition compatible and not cached."); return; diff --git a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs index f2ce89726fe..14baf52204c 100644 --- a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs @@ -51,6 +51,9 @@ public sealed class ImportModuleCommand : ModuleCmdletBase, IDisposable private const string ParameterSet_ViaPsrpSession = "PSSession"; private const string ParameterSet_ViaCimSession = "CimSession"; private const string ParameterSet_FQName_ViaPsrpSession = "FullyQualifiedNameAndPSSession"; + private const string ParameterSet_ViaWinCompat = "WinCompat"; + private const string ParameterSet_FQName_ViaWinCompat = "FullyQualifiedNameAndWinCompat"; + /// /// This parameter specifies whether to import to the current session state @@ -82,6 +85,7 @@ public string Prefix [Parameter(ParameterSetName = ParameterSet_Name, Mandatory = true, ValueFromPipeline = true, Position = 0)] [Parameter(ParameterSetName = ParameterSet_ViaPsrpSession, Mandatory = true, ValueFromPipeline = true, Position = 0)] [Parameter(ParameterSetName = ParameterSet_ViaCimSession, Mandatory = true, ValueFromPipeline = true, Position = 0)] + [Parameter("PSWindowsPowerShellCompatibility", ExperimentAction.Show, ParameterSetName = ParameterSet_ViaWinCompat, Mandatory = true, ValueFromPipeline = true, Position = 0)] [ValidateTrustedData] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] Name { set; get; } = Array.Empty(); @@ -91,6 +95,7 @@ public string Prefix /// [Parameter(ParameterSetName = ParameterSet_FQName, Mandatory = true, ValueFromPipeline = true, Position = 0)] [Parameter(ParameterSetName = ParameterSet_FQName_ViaPsrpSession, Mandatory = true, ValueFromPipeline = true, Position = 0)] + [Parameter("PSWindowsPowerShellCompatibility", ExperimentAction.Show, ParameterSetName = ParameterSet_FQName_ViaWinCompat, Mandatory = true, ValueFromPipeline = true, Position = 0)] [ValidateTrustedData] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public ModuleSpecification[] FullyQualifiedName { get; set; } @@ -226,8 +231,15 @@ public SwitchParameter Force /// /// Skips the check on CompatiblePSEditions for modules loaded from the System32 module path. + /// This is mutually exclusive with UseWindowsPowerShell parameter. /// - [Parameter] + [Parameter(ParameterSetName = ParameterSet_Name)] + [Parameter(ParameterSetName = ParameterSet_FQName)] + [Parameter(ParameterSetName = ParameterSet_ModuleInfo)] + [Parameter(ParameterSetName = ParameterSet_Assembly)] + [Parameter(ParameterSetName = ParameterSet_ViaPsrpSession)] + [Parameter(ParameterSetName = ParameterSet_ViaCimSession)] + [Parameter(ParameterSetName = ParameterSet_FQName_ViaPsrpSession)] public SwitchParameter SkipEditionCheck { get { return (SwitchParameter)BaseSkipEditionCheck; } @@ -263,6 +275,7 @@ public SwitchParameter AsCustomObject [Parameter(ParameterSetName = ParameterSet_Name)] [Parameter(ParameterSetName = ParameterSet_ViaPsrpSession)] [Parameter(ParameterSetName = ParameterSet_ViaCimSession)] + [Parameter("PSWindowsPowerShellCompatibility", ExperimentAction.Show, ParameterSetName = ParameterSet_ViaWinCompat)] [Alias("Version")] public Version MinimumVersion { @@ -277,6 +290,7 @@ public Version MinimumVersion [Parameter(ParameterSetName = ParameterSet_Name)] [Parameter(ParameterSetName = ParameterSet_ViaPsrpSession)] [Parameter(ParameterSetName = ParameterSet_ViaCimSession)] + [Parameter("PSWindowsPowerShellCompatibility", ExperimentAction.Show, ParameterSetName = ParameterSet_ViaWinCompat)] public string MaximumVersion { get @@ -306,6 +320,7 @@ public string MaximumVersion [Parameter(ParameterSetName = ParameterSet_Name)] [Parameter(ParameterSetName = ParameterSet_ViaPsrpSession)] [Parameter(ParameterSetName = ParameterSet_ViaCimSession)] + [Parameter("PSWindowsPowerShellCompatibility", ExperimentAction.Show, ParameterSetName = ParameterSet_ViaWinCompat)] public Version RequiredVersion { get { return BaseRequiredVersion; } @@ -406,6 +421,15 @@ public ImportModuleCommand() [ValidateNotNullOrEmpty] public string CimNamespace { get; set; } + /// + /// This parameter causes a module to be loaded into Windows PowerShell. + /// This is mutually exclusive with SkipEditionCheck parameter. + /// + [Parameter("PSWindowsPowerShellCompatibility", ExperimentAction.Show, ParameterSetName = ParameterSet_ViaWinCompat, Mandatory = true)] + [Parameter("PSWindowsPowerShellCompatibility", ExperimentAction.Show, ParameterSetName = ParameterSet_FQName_ViaWinCompat, Mandatory = true)] + [Alias("UseWinPS")] + public SwitchParameter UseWindowsPowerShell { get; set; } + #endregion Cmdlet parameters #region Local import @@ -805,7 +829,8 @@ private IList ImportModule_RemotelyViaPsrpSession( ImportModuleOptions importModuleOptions, IEnumerable moduleNames, IEnumerable fullyQualifiedNames, - PSSession psSession) + PSSession psSession, + bool usingWinCompat = false) { var remotelyImportedModules = new List(); if (moduleNames != null) @@ -829,7 +854,7 @@ private IList ImportModule_RemotelyViaPsrpSession( // Send telemetry on the imported modules foreach (PSModuleInfo moduleInfo in remotelyImportedModules) { - ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, moduleInfo.Name); + ApplicationInsightsTelemetry.SendTelemetryMetric(usingWinCompat ? TelemetryType.WinCompatModuleLoad : TelemetryType.ModuleLoad, moduleInfo.Name); } return remotelyImportedModules; @@ -1795,11 +1820,15 @@ protected override void ProcessRecord() { SetModuleBaseForEngineModules(foundModule.Name, this.Context); - // Telemetry here - report module load - ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, foundModule.Name); + // report loading of the module in telemetry + // avoid double reporting for WinCompat modules that go through CommandDiscovery\AutoloadSpecifiedModule + if (!foundModule.IsWindowsPowerShellCompatModule) + { + ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, foundModule.Name); #if LEGACYTELEMETRY - TelemetryAPI.ReportModuleLoad(foundModule); + TelemetryAPI.ReportModuleLoad(foundModule); #endif + } } } } @@ -1836,6 +1865,25 @@ protected override void ProcessRecord() ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, modulespec.Name); } } + else if (this.ParameterSetName.Equals(ParameterSet_ViaWinCompat, StringComparison.OrdinalIgnoreCase) + || this.ParameterSetName.Equals(ParameterSet_FQName_ViaWinCompat, StringComparison.OrdinalIgnoreCase)) + { + if (this.UseWindowsPowerShell) + { + var WindowsPowerShellCompatRemotingSession = CreateWindowsPowerShellCompatResources(); + if (WindowsPowerShellCompatRemotingSession != null) + { + foreach(PSModuleInfo moduleProxy in ImportModule_RemotelyViaPsrpSession(importModuleOptions, this.Name, this.FullyQualifiedName, WindowsPowerShellCompatRemotingSession, true)) + { + moduleProxy.IsWindowsPowerShellCompatModule = true; + System.Threading.Interlocked.Increment(ref s_WindowsPowerShellCompatUsageCounter); + + string message = StringUtil.Format(Modules.WinCompatModuleWarning, moduleProxy.Name, WindowsPowerShellCompatRemotingSession.Name); + WriteWarning(message); + } + } + } + } else { Dbg.Assert(false, "Unrecognized parameter set"); diff --git a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs index cd755e55e4a..68f613135b4 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs @@ -18,6 +18,7 @@ using System.Reflection; using System.Text; using System.Xml; +using System.Diagnostics; using Microsoft.PowerShell.Cmdletization; @@ -283,6 +284,21 @@ internal List MatchAll "Desktop" }; + /// + /// A counter for modules that are loaded using WindowsPS compat session. + /// + internal static int s_WindowsPowerShellCompatUsageCounter = 0; + + /// + /// Session name for WindowsPS compat remoting session. + /// + internal const string WindowsPowerShellCompatRemotingSessionName = "WinPSCompatSession"; + + /// + /// Synchronization object for creation/cleanup of WindowsPS compat remoting session. + /// + internal static object s_WindowsPowerShellCompatSyncObject = new object(); + private Dictionary _currentlyProcessingModules = new Dictionary(); internal bool LoadUsingModulePath(bool found, IEnumerable modulePath, string name, SessionState ss, @@ -2347,43 +2363,70 @@ internal PSModuleInfo LoadModuleManifest( bool isConsideredCompatible = ModuleUtils.IsPSEditionCompatible(moduleManifestPath, inferredCompatiblePSEditions); if (!BaseSkipEditionCheck && !isConsideredCompatible) { - containedErrors = true; - - if (writingErrors) + if (ExperimentalFeature.IsEnabled("PSWindowsPowerShellCompatibility")) { - message = StringUtil.Format( - Modules.PSEditionNotSupported, - moduleManifestPath, - PSVersionInfo.PSEdition, - string.Join(',', inferredCompatiblePSEditions)); + if (importingModule) + { + var commandInfo = new CmdletInfo("Import-Module", typeof(ImportModuleCommand)); + using var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + ps.AddCommand(commandInfo); + ps.AddParameter("PassThru", true); + ps.AddParameter("Name", moduleManifestPath); + ps.AddParameter("UseWindowsPowerShell", true); - InvalidOperationException ioe = new InvalidOperationException(message); + var moduleProxies = ps.Invoke(); - ErrorRecord er = new ErrorRecord( - ioe, - nameof(Modules) + "_" + nameof(Modules.PSEditionNotSupported), - ErrorCategory.ResourceUnavailable, - moduleManifestPath); - - WriteError(er); + // we are loading by a single ManifestPath so expect max of 1 + if(moduleProxies.Count > 0) + { + return moduleProxies[0]; + } + else + { + return null; + } + } } - - if (bailOnFirstError) + else { - // If we're trying to load the module, return null so that caches - // are not polluted - if (importingModule) + containedErrors = true; + + if (writingErrors) { - return null; + message = StringUtil.Format( + Modules.PSEditionNotSupported, + moduleManifestPath, + PSVersionInfo.PSEdition, + string.Join(',', inferredCompatiblePSEditions)); + + InvalidOperationException ioe = new InvalidOperationException(message); + + ErrorRecord er = new ErrorRecord( + ioe, + nameof(Modules) + "_" + nameof(Modules.PSEditionNotSupported), + ErrorCategory.ResourceUnavailable, + moduleManifestPath); + + WriteError(er); } - // If we return null with Get-Module, a fake module info will be created. Since - // we want to suppress output of the module, we need to do that here. - return new PSModuleInfo(moduleManifestPath, context: null, sessionState: null) + if (bailOnFirstError) { - HadErrorsLoading = true, - IsConsideredEditionCompatible = false, - }; + // If we're trying to load the module, return null so that caches + // are not polluted + if (importingModule) + { + return null; + } + + // If we return null with Get-Module, a fake module info will be created. Since + // we want to suppress output of the module, we need to do that here. + return new PSModuleInfo(moduleManifestPath, context: null, sessionState: null) + { + HadErrorsLoading = true, + IsConsideredEditionCompatible = false, + }; + } } } @@ -4768,6 +4811,62 @@ internal static Collection GetResolvedPathCollection(string filePath, Ex return filePaths; } + internal PSSession GetWindowsPowerShellCompatRemotingSession() + { + PSSession result = null; + var commandInfo = new CmdletInfo("Get-PSSession", typeof(GetPSSessionCommand)); + using var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + ps.AddCommand(commandInfo); + ps.AddParameter("Name", WindowsPowerShellCompatRemotingSessionName); + var results = ps.Invoke(); + if (results.Count > 0) + { + result = results[0]; + } + return result; + } + + internal PSSession CreateWindowsPowerShellCompatResources() + { + PSSession compatSession = null; + lock(s_WindowsPowerShellCompatSyncObject) + { + compatSession = GetWindowsPowerShellCompatRemotingSession(); + if (compatSession == null) + { + var commandInfo = new CmdletInfo("New-PSSession", typeof(NewPSSessionCommand)); + using var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + ps.AddCommand(commandInfo); + ps.AddParameter("UseWindowsPowerShell", true); + ps.AddParameter("Name", WindowsPowerShellCompatRemotingSessionName); + var results = ps.Invoke(); + if (results.Count > 0) + { + compatSession = results[0]; + System.Threading.Interlocked.Exchange(ref s_WindowsPowerShellCompatUsageCounter, 0); + } + } + } + + return compatSession; + } + + internal void CleanupWindowsPowerShellCompatResources() + { + lock(s_WindowsPowerShellCompatSyncObject) + { + var compatSession = GetWindowsPowerShellCompatRemotingSession(); + if (compatSession != null) + { + var commandInfo = new CmdletInfo("Remove-PSSession", typeof(RemovePSSessionCommand)); + using var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + ps.AddCommand(commandInfo); + ps.AddParameter("Session", compatSession); + ps.Invoke(); + } + } + } + private void RemoveTypesAndFormatting( IList formatFilesToRemove, IList typeFilesToRemove) @@ -4860,6 +4959,11 @@ internal void RemoveModule(PSModuleInfo module, string moduleNameInRemoveModuleC } } + if (ExperimentalFeature.IsEnabled("PSWindowsPowerShellCompatibility") && module.IsWindowsPowerShellCompatModule && (System.Threading.Interlocked.Decrement(ref s_WindowsPowerShellCompatUsageCounter) == 0)) + { + CleanupWindowsPowerShellCompatResources(); + } + // First remove cmdlets from the session state // (can't just go through module.ExportedCmdlets // because the names of the cmdlets might have been changed by the -Prefix parameter of Import-Module) diff --git a/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs b/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs index 6e2eb48ff44..11622e04680 100644 --- a/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs +++ b/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs @@ -282,6 +282,8 @@ internal void SetGuid(Guid guid) [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")] public string HelpInfoUri { get; private set; } + internal bool IsWindowsPowerShellCompatModule { get; set; } + internal void SetHelpInfoUri(string uri) { HelpInfoUri = uri; diff --git a/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs b/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs index 862b42297e8..5ea05bd2c0f 100644 --- a/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs +++ b/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs @@ -24,6 +24,7 @@ public sealed class PowerShellProcessInstance : IDisposable private bool _processExited; internal static readonly string PwshExePath; + internal static readonly string WinPwshExePath; #endregion Fields @@ -37,6 +38,7 @@ static PowerShellProcessInstance() PwshExePath = Path.Combine(Utils.DefaultPowerShellAppBase, "pwsh"); #else PwshExePath = Path.Combine(Utils.DefaultPowerShellAppBase, "pwsh.exe"); + WinPwshExePath = Path.Combine(Utils.GetApplicationBaseFromRegistry(Utils.DefaultPowerShellShellID), "powershell.exe"); #endif } @@ -50,11 +52,38 @@ static PowerShellProcessInstance() /// Specifies the initial working directory for the new powershell process. public PowerShellProcessInstance(Version powerShellVersion, PSCredential credential, ScriptBlock initializationScript, bool useWow64, string workingDirectory) { + string exePath = PwshExePath; + bool startingWindowsPowerShell51 = false; +#if !UNIX + // if requested PS version was "5.1" then we start Windows PS instead of PS Core + startingWindowsPowerShell51 = (powerShellVersion != null) && (powerShellVersion.Major == 5) && (powerShellVersion.Minor == 1); + if (startingWindowsPowerShell51) + { + exePath = WinPwshExePath; + + if (useWow64) + { + string procArch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); + + if ((!string.IsNullOrEmpty(procArch)) && (procArch.Equals("amd64", StringComparison.OrdinalIgnoreCase) || + procArch.Equals("ia64", StringComparison.OrdinalIgnoreCase))) + { + exePath = WinPwshExePath.ToLowerInvariant().Replace("\\system32\\", "\\syswow64\\"); + + if (!File.Exists(exePath)) + { + string message = PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.WowComponentNotPresent, exePath); + throw new PSInvalidOperationException(message); + } + } + } + } +#endif // 'WindowStyle' is used only if 'UseShellExecute' is 'true'. Since 'UseShellExecute' is set // to 'false' in our use, we can ignore the 'WindowStyle' setting in the initialization below. _startInfo = new ProcessStartInfo { - FileName = PwshExePath, + FileName = exePath, UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true, @@ -65,11 +94,17 @@ public PowerShellProcessInstance(Version powerShellVersion, PSCredential credent #endif }; + if (startingWindowsPowerShell51) + { + _startInfo.ArgumentList.Add("-Version"); + _startInfo.ArgumentList.Add("5.1"); + } + _startInfo.ArgumentList.Add("-s"); _startInfo.ArgumentList.Add("-NoLogo"); _startInfo.ArgumentList.Add("-NoProfile"); - if (!string.IsNullOrWhiteSpace(workingDirectory)) + if (!string.IsNullOrWhiteSpace(workingDirectory) && !startingWindowsPowerShell51) { _startInfo.ArgumentList.Add("-wd"); _startInfo.ArgumentList.Add(workingDirectory); diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs index 76f662983af..949cce46dc6 100644 --- a/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs +++ b/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs @@ -297,6 +297,10 @@ internal PSSession(RemoteRunspace remoteRunspace) ConfigurationName = "DefaultShell"; break; + case NewProcessConnectionInfo _: + ComputerType = TargetMachineType.RemoteMachine; + break; + default: Dbg.Assert(false, "Invalid Runspace"); break; diff --git a/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs b/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs index 1add9d8e09d..286337b9421 100644 --- a/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs +++ b/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs @@ -4010,6 +4010,11 @@ public sealed class EnablePSSessionConfigurationCommand : PSCmdlet function Test-WinRMQuickConfigNeeded {{ + # see issue #11005 - Function Test-WinRMQuickConfigNeeded needs to be updated: + # 1) currently this function always returns $True + # 2) checking for a firewall rule using Get-NetFirewallRule engages WinCompat code and has significant perf impact on Enable-PSRemoting; maybe change to Get-CimInstance -ClassName MSFT_NetFirewallRule + return $True + # Checking the following items #1. Starting or restarting (if already started) the WinRM service #2. Setting the WinRM service startup type to Automatic diff --git a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs index f7e149ba14e..3654a67c3fd 100644 --- a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs +++ b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs @@ -194,6 +194,11 @@ internal string GetMessage(string resourceString, params object[] args) /// protected const string SessionParameterSet = "Session"; + /// + /// Parameter set to use Windows PowerShell. + /// + protected const string UseWindowsPowerShellParameterSet = "UseWindowsPowerShellParameterSet"; + /// /// Default shellname. /// diff --git a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs index d2fea6b85df..40fd3126ff8 100644 --- a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs @@ -161,6 +161,13 @@ public override PSSession[] Session [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = NewPSSessionCommand.VMNameParameterSet)] public string ConfigurationName { get; set; } + + /// + /// Gets or sets parameter value that creates connection to a Windows PowerShell process. + /// + [Experimental("PSWindowsPowerShellCompatibility", ExperimentAction.Show)] + [Parameter(Mandatory = true, ParameterSetName = NewPSSessionCommand.UseWindowsPowerShellParameterSet)] + public SwitchParameter UseWindowsPowerShell { get; set; } #endregion Parameters @@ -257,6 +264,13 @@ protected override void ProcessRecord() break; + case NewPSSessionCommand.UseWindowsPowerShellParameterSet: + { + remoteRunspaces = CreateRunspacesForUseWindowsPowerShellParameterSet(); + } + + break; + default: { Dbg.Assert(false, "Missing parameter set in switch statement"); @@ -1136,6 +1150,28 @@ private List CreateRunspacesForSSHHostHashParameterSet() return remoteRunspaces; } + /// + /// Helper method to create remote runspace based on UseWindowsPowerShell parameter set. + /// + /// Remote runspace that was created. + private List CreateRunspacesForUseWindowsPowerShellParameterSet() + { + var remoteRunspaces = new List(); + + NewProcessConnectionInfo connectionInfo = new NewProcessConnectionInfo(this.Credential); + connectionInfo.AuthenticationMechanism = this.Authentication; + connectionInfo.PSVersion = new Version(5, 1); + + var typeTable = TypeTable.LoadDefaultTypeFiles(); + string runspaceName = GetRunspaceName(0, out int runspaceIdUnused); + remoteRunspaces.Add(RunspaceFactory.CreateRunspace(connectionInfo: connectionInfo, + host: this.Host, + typeTable: typeTable, + applicationArguments: null, + name: runspaceName) as RemoteRunspace); + return remoteRunspaces; + } + /// /// Helper method to either get a user supplied runspace/session name /// or to generate one along with a unique Id. diff --git a/src/System.Management.Automation/resources/Modules.resx b/src/System.Management.Automation/resources/Modules.resx index e0302d0b587..ac44a33e646 100644 --- a/src/System.Management.Automation/resources/Modules.resx +++ b/src/System.Management.Automation/resources/Modules.resx @@ -510,6 +510,9 @@ Some commands from module {0} cannot be imported over a CimSession. To get all the commands, verify that the remote server has PowerShell remote management enabled, and then try adding the PSSession parameter to an Import-Module cmdlet. + + Module {0} is loaded in Windows PowerShell using {1} remoting session; please note that all input and output of commands from this module will be deserialized objects. If you want to load this module into PowerShell Core please use 'Import-Module -SkipEditionCheck' syntax. + The module {0} cannot be imported over a CimSession. Try using the PSSession parameter of the Import-Module cmdlet. diff --git a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx index db4c55e986e..4cfd7203f63 100644 --- a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx +++ b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx @@ -1655,6 +1655,9 @@ All WinRM sessions connected to PowerShell session configurations, such as Micro PowerShell 6+ does not support WOW64. The binary must match the architecture of the processor. + + The "{0}" executable file was not found. Verify that the WOW64 feature is installed. + Unable to install plugin {0} to directory {1}. diff --git a/src/System.Management.Automation/utils/Telemetry.cs b/src/System.Management.Automation/utils/Telemetry.cs index 8551edfa863..bee35bca282 100644 --- a/src/System.Management.Automation/utils/Telemetry.cs +++ b/src/System.Management.Automation/utils/Telemetry.cs @@ -28,6 +28,12 @@ internal enum TelemetryType /// will be reported, otherwise it will be "anonymous". /// ModuleLoad, + + /// + /// Send telemetry when we load a module using Windows compatibility feature, only module names in the s_knownModules list + /// will be reported, otherwise it will be "anonymous". + /// + WinCompatModuleLoad, /// /// Send telemetry for experimental module feature activation. @@ -159,6 +165,7 @@ internal static void SendTelemetryMetric(TelemetryType metricId, string data) s_telemetryClient.GetMetric(metricName, "uuid", "SessionId", "Detail").TrackValue(metricValue: 1.0, s_uniqueUserIdentifier, s_sessionId, experimentalFeatureName); break; case TelemetryType.ModuleLoad: + case TelemetryType.WinCompatModuleLoad: string moduleName = GetModuleName(data); // This will return anonymous if the modulename is not on the report list s_telemetryClient.GetMetric(metricName, "uuid", "SessionId", "Detail").TrackValue(metricValue: 1.0, s_uniqueUserIdentifier, s_sessionId, moduleName); break; diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/CompatiblePSEditions.Module.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/CompatiblePSEditions.Module.Tests.ps1 index 73e4c0ce41b..43069749824 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/CompatiblePSEditions.Module.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/CompatiblePSEditions.Module.Tests.ps1 @@ -41,7 +41,7 @@ function New-EditionCompatibleModule New-Item -Path $modulePath -ItemType Directory - New-Item -Path $psm1Path -Value "function Test-$ModuleName { `$true }" + New-Item -Path $psm1Path -Value "function Test-$ModuleName { `$true } function Test-${ModuleName}PSEdition { `$PSVersionTable.PSEdition }" -Force if ($CompatiblePSEditions) { @@ -117,11 +117,11 @@ function New-TestNestedModule if ($UseRootModule) { $newManifestCmd += "-RootModule $RootModuleFilename " - $newManifestCmd += "-FunctionsToExport @('Test-RootModule') " + $newManifestCmd += "-FunctionsToExport @('Test-RootModule','Test-RootModulePSEdition') " } else { - $newManifestCmd += "-FunctionsToExport @('Test-ScriptModule') " + $newManifestCmd += "-FunctionsToExport @('Test-ScriptModule','Test-ScriptModulePSEdition') " } $newManifestCmd += "-CmdletsToExport @() -VariablesToExport @() -AliasesToExport @() " @@ -248,6 +248,10 @@ Describe "Import-Module from CompatiblePSEditions-checked paths" -Tag "CI" { $allModules = ($successCases + $failCases).ModuleName + # make sure there are no ImplicitRemoting leftovers from previous tests + Get-Module | Where-Object {$_.PrivateData.ImplicitRemoting} | Remove-Module -Force + Get-PSSession -Name WinPSCompatSession -ErrorAction SilentlyContinue | Remove-PSSession + # Emulate the System32 module path for tests [System.Management.Automation.Internal.InternalTestHooks]::SetTestHook("TestWindowsPowerShellPSHomeLocation", $basePath) } @@ -276,12 +280,11 @@ Describe "Import-Module from CompatiblePSEditions-checked paths" -Tag "CI" { & "Test-$ModuleName" | Should -Be $Result } - It "Fails to import incompatible modules from the module path with PSEdition " -TestCases $failCases -Skip:(-not $IsWindows) { + It "Successfully imports incompatible modules from the module path with PSEdition using WinCompat" -TestCases $failCases -Skip:(-not $IsWindows) { param($Editions, $ModuleName, $Result) - { - Import-Module $ModuleName -Force -ErrorAction 'Stop'; & "Test-$ModuleName" - } | Should -Throw -ErrorId "Modules_PSEditionNotSupported,Microsoft.PowerShell.Commands.ImportModuleCommand" + Import-Module $ModuleName -Force -ErrorAction 'Stop' + & "Test-$ModuleName" | Should -Be $Result } It "Imports an incompatible module from the module path with -SkipEditionCheck with PSEdition " -TestCases ($successCases + $failCases) -Skip:(-not $IsWindows) { @@ -290,6 +293,13 @@ Describe "Import-Module from CompatiblePSEditions-checked paths" -Tag "CI" { Import-Module $ModuleName -SkipEditionCheck -Force & "Test-$ModuleName" | Should -Be $Result } + + It "Imports any module using WinCompat from the module path with -UseWindowsPowerShell with PSEdition " -TestCases ($successCases + $failCases) -Skip:(-not $IsWindows) { + param($Editions, $ModuleName, $Result) + + Import-Module $ModuleName -UseWindowsPowerShell -Force + & "Test-${ModuleName}PSEdition" | Should -Be 'Desktop' + } } Context "Imports from absolute path" { @@ -302,14 +312,13 @@ Describe "Import-Module from CompatiblePSEditions-checked paths" -Tag "CI" { & "Test-$ModuleName" | Should -Be $Result } - It "Fails to import incompatible modules from an absolute path with PSEdition " -TestCases $failCases -Skip:(-not $IsWindows) { + It "Successfully imports incompatible modules from an absolute path with PSEdition using WinCompat" -TestCases $failCases -Skip:(-not $IsWindows) { param($Editions, $ModuleName, $Result) $path = Join-Path -Path $basePath -ChildPath $ModuleName - { - Import-Module $path -Force -ErrorAction 'Stop'; & "Test-$ModuleName" - } | Should -Throw -ErrorId "Modules_PSEditionNotSupported,Microsoft.PowerShell.Commands.ImportModuleCommand" + Import-Module $path -Force -ErrorAction 'Stop' + & "Test-$ModuleName" | Should -Be $Result } It "Imports an incompatible module from an absolute path with -SkipEditionCheck with PSEdition " -TestCases ($successCases + $failCases) -Skip:(-not $IsWindows) { @@ -320,6 +329,37 @@ Describe "Import-Module from CompatiblePSEditions-checked paths" -Tag "CI" { Import-Module $path -SkipEditionCheck -Force & "Test-$ModuleName" | Should -Be $Result } + + It "Imports any module using WinCompat from an absolute path with -UseWindowsPowerShell with PSEdition " -TestCases ($successCases + $failCases) -Skip:(-not $IsWindows) { + param($Editions, $ModuleName, $Result) + + $path = Join-Path -Path $basePath -ChildPath $ModuleName + + Import-Module $path -UseWindowsPowerShell -Force + & "Test-${ModuleName}PSEdition" | Should -Be 'Desktop' + } + } + + Context "Imports using CommandDiscovery\ModuleAutoload" { + BeforeAll { + Add-ModulePath $basePath + } + + AfterAll { + Restore-ModulePath + } + + It "Successfully auto-imports compatible modules from the module path with PSEdition " -TestCases $successCases -Skip:(-not $IsWindows) { + param($Editions, $ModuleName, $Result) + + & "Test-$ModuleName" | Should -Be $Result + } + + It "Successfully auto-imports incompatible modules from the module path with PSEdition using WinCompat" -TestCases $failCases -Skip:(-not $IsWindows) { + param($Editions, $ModuleName, $Result) + + & "Test-${ModuleName}PSEdition" | Should -Be 'Desktop' + } } } @@ -673,6 +713,7 @@ Describe "Import-Module nested module behaviour with Edition checking" -Tag "Fea UseRootModule = @($true, $false) UseAbsolutePath = @($true, $false) MarkedEdition = @($null, "Desktop", "Core", @("Desktop","Core")) + UseWindowsPowerShell = @($true, $false) } # Combine all the test conditions into a list of test cases @@ -693,12 +734,12 @@ Describe "Import-Module nested module behaviour with Edition checking" -Tag "Fea # Define nested script module $scriptModuleName = "NestedScriptModule" $scriptModuleFile = "$scriptModuleName.psm1" - $scriptModuleContent = 'function Test-ScriptModule { return $true }' + $scriptModuleContent = 'function Test-ScriptModule { return $true } function Test-ScriptModulePSEdition { $PSVersionTable.PSEdition }' # Define root module definition $rootModuleName = "RootModule" $rootModuleFile = "$rootModuleName.psm1" - $rootModuleContent = 'function Test-RootModule { Test-ScriptModule }' + $rootModuleContent = 'function Test-RootModule { Test-ScriptModule } function Test-RootModulePSEdition { Test-ScriptModulePSEdition }' # Module directory structure: $TestDrive/$compatibility/$guid/$moduleName/{module parts} $compatibleDir = "Compatible" @@ -710,6 +751,10 @@ Describe "Import-Module nested module behaviour with Edition checking" -Tag "Fea { New-Item -Path $basePath -ItemType Directory } + + # make sure there are no ImplicitRemoting leftovers from previous tests + Get-Module | Where-Object {$_.PrivateData.ImplicitRemoting} | Remove-Module -Force + Get-PSSession -Name WinPSCompatSession -ErrorAction SilentlyContinue | Remove-PSSession } Context "Modules ON the System32 test path" { @@ -737,8 +782,8 @@ Describe "Import-Module nested module behaviour with Edition checking" -Tag "Fea Restore-ModulePath } - It "Import-Module when SkipEditionCheck: , using root module: , using absolute path: , CompatiblePSEditions: " -TestCases $testCases -Skip:(-not $IsWindows) { - param([bool]$SkipEditionCheck, [bool]$UseRootModule, [bool]$UseAbsolutePath, [string[]]$MarkedEdition) + It "Import-Module when SkipEditionCheck: , using root module: , using absolute path: , CompatiblePSEditions: , UseWindowsPowerShell: " -TestCases $testCases -Skip:(-not $IsWindows) { + param([bool]$SkipEditionCheck, [bool]$UseRootModule, [bool]$UseAbsolutePath, [string[]]$MarkedEdition, [bool]$UseWindowsPowerShell) New-TestNestedModule ` -ModuleBase $moduleBase ` @@ -754,16 +799,25 @@ Describe "Import-Module nested module behaviour with Edition checking" -Tag "Fea { if ((-not $SkipEditionCheck) -and (-not ($MarkedEdition -contains "Core"))) { - { - Import-Module $moduleBase -ErrorAction Stop - } | Should -Throw -ErrorId "Modules_PSEditionNotSupported,Microsoft.PowerShell.Commands.ImportModuleCommand" + # this goes through WinCompat code + { Import-Module $moduleBase -ErrorAction Stop } | Should Not Throw + Get-Module -Name $moduleName | Should Not BeNullOrEmpty return } - if ($SkipEditionCheck) + if ($SkipEditionCheck -and $UseWindowsPowerShell) + { + { Import-Module $moduleBase -SkipEditionCheck -UseWindowsPowerShell } | Should -Throw -ErrorId "AmbiguousParameterSet" + return + } + elseif ($SkipEditionCheck) { Import-Module $moduleBase -SkipEditionCheck } + elseif ($UseWindowsPowerShell) + { + Import-Module $moduleBase -UseWindowsPowerShell + } else { Import-Module $moduleBase @@ -773,26 +827,46 @@ Describe "Import-Module nested module behaviour with Edition checking" -Tag "Fea { Test-RootModule | Should -BeTrue { Test-ScriptModule } | Should -Throw -ErrorId "CommandNotFoundException" + if ($UseWindowsPowerShell) + { + Test-RootModulePSEdition | Should -Be 'Desktop' + { Test-ScriptModulePSEdition } | Should -Throw -ErrorId "CommandNotFoundException" + } return } Test-ScriptModule | Should -BeTrue { Test-RootModule } | Should -Throw -ErrorId "CommandNotFoundException" + if ($UseWindowsPowerShell) + { + Test-ScriptModulePSEdition | Should -Be 'Desktop' + { Test-RootModulePSEdition } | Should -Throw -ErrorId "CommandNotFoundException" + } return } if ((-not $SkipEditionCheck) -and (-not ($MarkedEdition -contains "Core"))) { - { - Import-Module $moduleName -ErrorAction Stop - } | Should -Throw -ErrorId "Modules_PSEditionNotSupported,Microsoft.PowerShell.Commands.ImportModuleCommand" + # this goes through WinCompat code + { Import-Module $moduleName -ErrorAction Stop } | Should Not Throw + Get-Module -Name $moduleName | Should Not BeNullOrEmpty return } - if ($SkipEditionCheck) + + if ($SkipEditionCheck -and $UseWindowsPowerShell) + { + { Import-Module $moduleName -SkipEditionCheck -UseWindowsPowerShell } | Should -Throw -ErrorId "AmbiguousParameterSet" + return + } + elseif ($SkipEditionCheck) { Import-Module $moduleName -SkipEditionCheck } + elseif ($UseWindowsPowerShell) + { + Import-Module $moduleName -UseWindowsPowerShell + } else { Import-Module $moduleName @@ -802,11 +876,21 @@ Describe "Import-Module nested module behaviour with Edition checking" -Tag "Fea { Test-RootModule | Should -BeTrue { Test-ScriptModule } | Should -Throw -ErrorId "CommandNotFoundException" + if ($UseWindowsPowerShell) + { + Test-RootModulePSEdition | Should -Be 'Desktop' + { Test-ScriptModulePSEdition } | Should -Throw -ErrorId "CommandNotFoundException" + } return } Test-ScriptModule | Should -BeTrue { Test-RootModule } | Should -Throw -ErrorId "CommandNotFoundException" + if ($UseWindowsPowerShell) + { + Test-ScriptModulePSEdition | Should -Be 'Desktop' + { Test-RootModulePSEdition } | Should -Throw -ErrorId "CommandNotFoundException" + } } } @@ -827,8 +911,13 @@ Describe "Import-Module nested module behaviour with Edition checking" -Tag "Fea Restore-ModulePath } - It "Import-Module when SkipEditionCheck: , using root module: , using absolute path: , CompatiblePSEditions: " -TestCases $testCases { - param([bool]$SkipEditionCheck, [bool]$UseRootModule, [bool]$UseAbsolutePath, [string[]]$MarkedEdition) + It "Import-Module when SkipEditionCheck: , using root module: , using absolute path: , CompatiblePSEditions: , UseWindowsPowerShell: " -TestCases $testCases { + param([bool]$SkipEditionCheck, [bool]$UseRootModule, [bool]$UseAbsolutePath, [string[]]$MarkedEdition, [bool]$UseWindowsPowerShell) + + if ($UseWindowsPowerShell -and (-not $IsWindows)) + { + Set-ItResult -Skipped -Because 'UseWindowsPowerShell parameter is supported only on Windows' + } New-TestNestedModule ` -ModuleBase $moduleBase ` @@ -842,19 +931,37 @@ Describe "Import-Module nested module behaviour with Edition checking" -Tag "Fea if ($UseAbsolutePath) { - if ($SkipEditionCheck) + if ($SkipEditionCheck -and $UseWindowsPowerShell) + { + { Import-Module $moduleBase -SkipEditionCheck -UseWindowsPowerShell } | Should -Throw -ErrorId "AmbiguousParameterSet" + return + } + elseif ($SkipEditionCheck) { Import-Module $moduleBase -SkipEditionCheck } + elseif ($UseWindowsPowerShell) + { + Import-Module $moduleBase -UseWindowsPowerShell + } else { Import-Module $moduleBase } } + elseif ($SkipEditionCheck -and $UseWindowsPowerShell) + { + { Import-Module $moduleName -SkipEditionCheck -UseWindowsPowerShell } | Should -Throw -ErrorId "AmbiguousParameterSet" + return + } elseif ($SkipEditionCheck) { Import-Module $moduleName -SkipEditionCheck } + elseif ($UseWindowsPowerShell) + { + Import-Module $moduleName -UseWindowsPowerShell + } else { Import-Module $moduleName @@ -864,11 +971,21 @@ Describe "Import-Module nested module behaviour with Edition checking" -Tag "Fea { Test-RootModule | Should -BeTrue { Test-ScriptModule } | Should -Throw -ErrorId "CommandNotFoundException" + if ($UseWindowsPowerShell) + { + Test-RootModulePSEdition | Should -Be 'Desktop' + { Test-ScriptModulePSEdition } | Should -Throw -ErrorId "CommandNotFoundException" + } return } Test-ScriptModule | Should -BeTrue { Test-RootModule } | Should -Throw -ErrorId "CommandNotFoundException" + if ($UseWindowsPowerShell) + { + Test-ScriptModulePSEdition | Should -Be 'Desktop' + { Test-RootModulePSEdition } | Should -Throw -ErrorId "CommandNotFoundException" + } } } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Enter-PSHostProcess.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Enter-PSHostProcess.Tests.ps1 index 8ee74c6bda8..970d0d7fe5f 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/Enter-PSHostProcess.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Enter-PSHostProcess.Tests.ps1 @@ -97,9 +97,9 @@ Describe "Enter-PSHostProcess tests" -Tag Feature { } It "Can enter, exit, and re-enter another Windows PowerShell PSHost" -Skip:(!$IsWindows) { - # Start a Windows PowerShell job where the first thing it does is return $pid. After that, spin forever. + # Start a PowerShell job where the first thing it does is return $pid. After that, spin forever. # We will use this job as the target process for Enter-PSHostProcess - $powershellJob = Start-Job -PSVersion 5.1 { + $powershellJob = Start-Job { $pid while ($true) { Start-Sleep -Seconds 30 | Out-Null diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Process.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Process.Tests.ps1 index ed35c6437cc..8eacdb06cde 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Process.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Process.Tests.ps1 @@ -92,7 +92,16 @@ Describe "Get-Process" -Tags "CI" { } Describe "Get-Process Formatting" -Tags "Feature" { - It "Should not have Handle in table format header" { + BeforeAll { + $skip = $false + if ($IsWindows) + { + # on Windows skip this test until issue #11016 is resolved + $skip = $true + } + } + + It "Should not have Handle in table format header" -Skip:$skip { $types = "System.Diagnostics.Process","System.Diagnostics.Process#IncludeUserName" foreach ($type in $types) { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 index cc335afc8de..620ee95dded 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 @@ -754,7 +754,7 @@ try } It "Helper functions should not be imported" { - (Get-Item function:*PSImplicitRemoting* -ErrorAction SilentlyContinue) | Should -BeNullOrEmpty + Get-Item function:*PSImplicitRemoting* -ErrorAction SilentlyContinue | Where-Object {$_.ModuleName -eq $module.Name} | Should -BeNullOrEmpty } It "Calls implicit remoting proxies 'MyFunction'" { @@ -813,7 +813,7 @@ try } It "Private functions from the implicit remoting module shouldn't get imported into global scope" { - @(Get-ChildItem function:*Implicit* -ErrorAction SilentlyContinue).Count | Should -Be 0 + @(Get-ChildItem function:*Implicit* -ErrorAction SilentlyContinue | Where-Object {$_.ModuleName -eq $module.Name}).Count | Should -Be 0 } } } @@ -1870,7 +1870,7 @@ try $oldNumberOfHandlers | Should -Be $newNumberOfHandlers ## Private functions from the implicit remoting module shouldn't get imported into global scope - @(Get-ChildItem function:*Implicit* -ErrorAction SilentlyContinue).Count | Should -Be 0 + @(Get-ChildItem function:*Implicit* -ErrorAction SilentlyContinue | Where-Object {$_.ModuleName -eq $module.Name}).Count | Should -Be 0 } } diff --git a/test/powershell/Modules/ThreadJob/ThreadJob.Tests.ps1 b/test/powershell/Modules/ThreadJob/ThreadJob.Tests.ps1 index 987c2be13cf..33ca2755110 100644 --- a/test/powershell/Modules/ThreadJob/ThreadJob.Tests.ps1 +++ b/test/powershell/Modules/ThreadJob/ThreadJob.Tests.ps1 @@ -264,7 +264,7 @@ Describe 'Basic ThreadJob Tests' -Tags 'CI' { It 'ThreadJob Runspaces should be cleaned up at completion' { $script = $WaitForCountFnScript + @' - + $WarningPreference = 'SilentlyContinue' try { Get-Job | Where-Object PSJobTypeName -eq "ThreadJob" | Remove-Job -Force @@ -296,7 +296,7 @@ Describe 'Basic ThreadJob Tests' -Tags 'CI' { It 'ThreadJob Runspaces should be cleaned up after job removal' { $script = $WaitForCountFnScript + @' - + $WarningPreference = 'SilentlyContinue' try { Get-Job | Where-Object PSJobTypeName -eq "ThreadJob" | Remove-Job -Force $rsStartCount = @(Get-Runspace).Count diff --git a/test/powershell/Provider/Pester.AutomountedDrives.Tests.ps1 b/test/powershell/Provider/Pester.AutomountedDrives.Tests.ps1 index 4884f3abb92..487b99b1fd7 100644 --- a/test/powershell/Provider/Pester.AutomountedDrives.Tests.ps1 +++ b/test/powershell/Provider/Pester.AutomountedDrives.Tests.ps1 @@ -27,6 +27,8 @@ Describe "Test suite for validating automounted PowerShell drives" -Tags @('Feat $tmpVhdPath = Join-Path $TestDrive 'TestVHD.vhd' New-VHD -path $tmpVhdPath -SizeBytes 5mb -Dynamic -ErrorAction Stop Remove-Item $tmpVhdPath + $VHDToolsNotFound = (Get-Module Hyper-V).PrivateData.ImplicitRemoting -eq $True + Remove-Module Hyper-V } catch { $VHDToolsNotFound = $true } diff --git a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 index 0f07090ff5f..8e1bffaf40d 100644 --- a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 +++ b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 @@ -124,7 +124,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Alias", "npssc", "New-PSSessionConfigurationFile", $($FullCLR ), "ReadOnly", "", "" "Alias", "nsn", "New-PSSession", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "" "Alias", "nv", "New-Variable", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" -"Alias", "nwsn", "New-PSWorkflowSession", $($FullCLR ), "ReadOnly", "", "" +"Alias", "nwsn", "New-PSWorkflowSession", $($FullCLR -or $CoreWindows ), "", "", "" "Alias", "ogv", "Out-GridView", $($FullCLR -or $CoreWindows ), "ReadOnly", "", "" "Alias", "oh", "Out-Host", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" "Alias", "popd", "Pop-Location", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "AllScope", "" diff --git a/test/powershell/engine/Help/HelpSystem.OnlineHelp.Tests.ps1 b/test/powershell/engine/Help/HelpSystem.OnlineHelp.Tests.ps1 index 38005cae2b6..ace83809e0f 100644 --- a/test/powershell/engine/Help/HelpSystem.OnlineHelp.Tests.ps1 +++ b/test/powershell/engine/Help/HelpSystem.OnlineHelp.Tests.ps1 @@ -31,7 +31,13 @@ Describe 'Online help tests for PowerShell Cmdlets' -Tags "CI" { foreach ($cmdlet in $cmdletList) { # If the cmdlet is not preset in CoreCLR, skip it. - $skipTest = $null -eq (Get-Command $cmdlet.TopicTitle -ErrorAction SilentlyContinue) + $command = Get-Command $cmdlet.TopicTitle -ErrorAction SilentlyContinue + $skipTest = $null -eq ($command) + if ((-not $skipTest) -and $command.Module.PrivateData.ImplicitRemoting) + { + $skipTest = $true + Remove-Module $command.Module + } # TopicTitle - is the cmdlet name in the csv file # HelpURI - is the expected help URI in the csv file