Support using non-compatible Windows PowerShell modules in PowerShell Core (#10973)

This commit is contained in:
Andrew
2019-11-18 10:44:55 -08:00
committed by Dongbo Wang
parent 6dda2308fe
commit b218e6f6cc
21 changed files with 469 additions and 78 deletions
@@ -134,7 +134,12 @@ namespace System.Management.Automation
#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<ExperimentalFeature>(engineFeatures);
@@ -103,7 +103,7 @@ namespace System.Management.Automation
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 @@ namespace System.Management.Automation
// 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;
@@ -51,6 +51,9 @@ namespace Microsoft.PowerShell.Commands
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";
/// <summary>
/// This parameter specifies whether to import to the current session state
@@ -82,6 +85,7 @@ namespace Microsoft.PowerShell.Commands
[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<string>();
@@ -91,6 +95,7 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
[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 @@ namespace Microsoft.PowerShell.Commands
/// <summary>
/// Skips the check on CompatiblePSEditions for modules loaded from the System32 module path.
/// This is mutually exclusive with UseWindowsPowerShell parameter.
/// </summary>
[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 @@ namespace Microsoft.PowerShell.Commands
[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 @@ namespace Microsoft.PowerShell.Commands
[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 @@ namespace Microsoft.PowerShell.Commands
[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 @@ namespace Microsoft.PowerShell.Commands
[ValidateNotNullOrEmpty]
public string CimNamespace { get; set; }
/// <summary>
/// This parameter causes a module to be loaded into Windows PowerShell.
/// This is mutually exclusive with SkipEditionCheck parameter.
/// </summary>
[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 @@ namespace Microsoft.PowerShell.Commands
ImportModuleOptions importModuleOptions,
IEnumerable<string> moduleNames,
IEnumerable<ModuleSpecification> fullyQualifiedNames,
PSSession psSession)
PSSession psSession,
bool usingWinCompat = false)
{
var remotelyImportedModules = new List<PSModuleInfo>();
if (moduleNames != null)
@@ -829,7 +854,7 @@ namespace Microsoft.PowerShell.Commands
// 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 @@ namespace Microsoft.PowerShell.Commands
{
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 @@ namespace Microsoft.PowerShell.Commands
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");
@@ -18,6 +18,7 @@ using System.Management.Automation.Security;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Diagnostics;
using Microsoft.PowerShell.Cmdletization;
@@ -283,6 +284,21 @@ namespace Microsoft.PowerShell.Commands
"Desktop"
};
/// <summary>
/// A counter for modules that are loaded using WindowsPS compat session.
/// </summary>
internal static int s_WindowsPowerShellCompatUsageCounter = 0;
/// <summary>
/// Session name for WindowsPS compat remoting session.
/// </summary>
internal const string WindowsPowerShellCompatRemotingSessionName = "WinPSCompatSession";
/// <summary>
/// Synchronization object for creation/cleanup of WindowsPS compat remoting session.
/// </summary>
internal static object s_WindowsPowerShellCompatSyncObject = new object();
private Dictionary<string, PSModuleInfo> _currentlyProcessingModules = new Dictionary<string, PSModuleInfo>();
internal bool LoadUsingModulePath(bool found, IEnumerable<string> modulePath, string name, SessionState ss,
@@ -2347,43 +2363,70 @@ namespace Microsoft.PowerShell.Commands
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));
InvalidOperationException ioe = new InvalidOperationException(message);
ErrorRecord er = new ErrorRecord(
ioe,
nameof(Modules) + "_" + nameof(Modules.PSEditionNotSupported),
ErrorCategory.ResourceUnavailable,
moduleManifestPath);
WriteError(er);
}
if (bailOnFirstError)
{
// If we're trying to load the module, return null so that caches
// are not polluted
if (importingModule)
{
return null;
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);
var moduleProxies = ps.Invoke<PSModuleInfo>();
// we are loading by a single ManifestPath so expect max of 1
if(moduleProxies.Count > 0)
{
return moduleProxies[0];
}
else
{
return null;
}
}
}
else
{
containedErrors = true;
if (writingErrors)
{
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 @@ namespace Microsoft.PowerShell.Commands
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<PSSession>();
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<PSSession>();
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<string> formatFilesToRemove,
IList<string> typeFilesToRemove)
@@ -4860,6 +4959,11 @@ namespace Microsoft.PowerShell.Commands
}
}
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)
@@ -282,6 +282,8 @@ namespace System.Management.Automation
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")]
public string HelpInfoUri { get; private set; }
internal bool IsWindowsPowerShellCompatModule { get; set; }
internal void SetHelpInfoUri(string uri)
{
HelpInfoUri = uri;
@@ -24,6 +24,7 @@ namespace System.Management.Automation.Runspaces
private bool _processExited;
internal static readonly string PwshExePath;
internal static readonly string WinPwshExePath;
#endregion Fields
@@ -37,6 +38,7 @@ namespace System.Management.Automation.Runspaces
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 @@ namespace System.Management.Automation.Runspaces
/// <param name="workingDirectory">Specifies the initial working directory for the new powershell process.</param>
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 @@ namespace System.Management.Automation.Runspaces
#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);
@@ -297,6 +297,10 @@ namespace System.Management.Automation.Runspaces
ConfigurationName = "DefaultShell";
break;
case NewProcessConnectionInfo _:
ComputerType = TargetMachineType.RemoteMachine;
break;
default:
Dbg.Assert(false, "Invalid Runspace");
break;
@@ -4010,6 +4010,11 @@ Set-PSSessionConfiguration $args[0] $args[1] $args[2] $args[3] $args[4] $args[5]
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
@@ -194,6 +194,11 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
protected const string SessionParameterSet = "Session";
/// <summary>
/// Parameter set to use Windows PowerShell.
/// </summary>
protected const string UseWindowsPowerShellParameterSet = "UseWindowsPowerShellParameterSet";
/// <summary>
/// Default shellname.
/// </summary>
@@ -161,6 +161,13 @@ namespace Microsoft.PowerShell.Commands
[Parameter(ValueFromPipelineByPropertyName = true,
ParameterSetName = NewPSSessionCommand.VMNameParameterSet)]
public string ConfigurationName { get; set; }
/// <summary>
/// Gets or sets parameter value that creates connection to a Windows PowerShell process.
/// </summary>
[Experimental("PSWindowsPowerShellCompatibility", ExperimentAction.Show)]
[Parameter(Mandatory = true, ParameterSetName = NewPSSessionCommand.UseWindowsPowerShellParameterSet)]
public SwitchParameter UseWindowsPowerShell { get; set; }
#endregion Parameters
@@ -257,6 +264,13 @@ namespace Microsoft.PowerShell.Commands
break;
case NewPSSessionCommand.UseWindowsPowerShellParameterSet:
{
remoteRunspaces = CreateRunspacesForUseWindowsPowerShellParameterSet();
}
break;
default:
{
Dbg.Assert(false, "Missing parameter set in switch statement");
@@ -1136,6 +1150,28 @@ namespace Microsoft.PowerShell.Commands
return remoteRunspaces;
}
/// <summary>
/// Helper method to create remote runspace based on UseWindowsPowerShell parameter set.
/// </summary>
/// <returns>Remote runspace that was created.</returns>
private List<RemoteRunspace> CreateRunspacesForUseWindowsPowerShellParameterSet()
{
var remoteRunspaces = new List<RemoteRunspace>();
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;
}
/// <summary>
/// Helper method to either get a user supplied runspace/session name
/// or to generate one along with a unique Id.
@@ -510,6 +510,9 @@
<data name="MixedModuleOverCimSessionWarning" xml:space="preserve">
<value>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.</value>
</data>
<data name="WinCompatModuleWarning" xml:space="preserve">
<value>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.</value>
</data>
<data name="PsModuleOverCimSessionError" xml:space="preserve">
<value>The module {0} cannot be imported over a CimSession. Try using the PSSession parameter of the Import-Module cmdlet.</value>
</data>
@@ -1655,6 +1655,9 @@ All WinRM sessions connected to PowerShell session configurations, such as Micro
<data name="InvalidProcessorArchitecture" xml:space="preserve">
<value>PowerShell 6+ does not support WOW64. The binary must match the architecture of the processor.</value>
</data>
<data name="WowComponentNotPresent" xml:space="preserve">
<value>The "{0}" executable file was not found. Verify that the WOW64 feature is installed.</value>
</data>
<data name="UnableToInstallPlugin" xml:space="preserve">
<value>Unable to install plugin {0} to directory {1}.</value>
</data>
@@ -28,6 +28,12 @@ namespace Microsoft.PowerShell.Telemetry
/// will be reported, otherwise it will be "anonymous".
/// </summary>
ModuleLoad,
/// <summary>
/// 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".
/// </summary>
WinCompatModuleLoad,
/// <summary>
/// Send telemetry for experimental module feature activation.
@@ -159,6 +165,7 @@ namespace Microsoft.PowerShell.Telemetry
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;
@@ -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 <Editions>" -TestCases $failCases -Skip:(-not $IsWindows) {
It "Successfully imports incompatible modules from the module path with PSEdition <Editions> 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 <Editions>" -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 <Editions>" -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 <Editions>" -TestCases $failCases -Skip:(-not $IsWindows) {
It "Successfully imports incompatible modules from an absolute path with PSEdition <Editions> 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 <Editions>" -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 <Editions>" -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 <Editions>" -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 <Editions> 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: <SkipEditionCheck>, using root module: <UseRootModule>, using absolute path: <UseAbsolutePath>, CompatiblePSEditions: <MarkedEdition>" -TestCases $testCases -Skip:(-not $IsWindows) {
param([bool]$SkipEditionCheck, [bool]$UseRootModule, [bool]$UseAbsolutePath, [string[]]$MarkedEdition)
It "Import-Module when SkipEditionCheck: <SkipEditionCheck>, using root module: <UseRootModule>, using absolute path: <UseAbsolutePath>, CompatiblePSEditions: <MarkedEdition>, UseWindowsPowerShell: <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: <SkipEditionCheck>, using root module: <UseRootModule>, using absolute path: <UseAbsolutePath>, CompatiblePSEditions: <MarkedEdition>" -TestCases $testCases {
param([bool]$SkipEditionCheck, [bool]$UseRootModule, [bool]$UseAbsolutePath, [string[]]$MarkedEdition)
It "Import-Module when SkipEditionCheck: <SkipEditionCheck>, using root module: <UseRootModule>, using absolute path: <UseAbsolutePath>, CompatiblePSEditions: <MarkedEdition>, UseWindowsPowerShell: <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"
}
}
}
}
@@ -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
@@ -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) {
@@ -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
}
}
@@ -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
@@ -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 }
@@ -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", ""
@@ -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