From 0c46e3e71d628b96833da0299890be89fae430bf Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 10 Dec 2019 10:40:13 -0800 Subject: [PATCH] Added `WindowsPS` version check for `WinCompat` (#11148) --- .../engine/Modules/ImportModuleCommand.cs | 13 ++++++- .../engine/Utils.cs | 38 +++++++++++++++++++ .../remoting/commands/newrunspacecommand.cs | 3 +- .../resources/Modules.resx | 3 ++ .../CompatiblePSEditions.Module.Tests.ps1 | 29 +++++++++++++- 5 files changed, 82 insertions(+), 4 deletions(-) diff --git a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs index 6f160e2490..12db5ca33f 100644 --- a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs @@ -1881,13 +1881,22 @@ namespace Microsoft.PowerShell.Commands internal override IList ImportModulesUsingWinCompat(IEnumerable moduleNames, IEnumerable moduleFullyQualifiedNames, ImportModuleOptions importModuleOptions) { + IList moduleProxyList = new List(); +#if !UNIX + var winPSVersionString = Utils.GetWindowsPowerShellVersionFromRegistry(); + if (!winPSVersionString.StartsWith("5.1", StringComparison.OrdinalIgnoreCase)) + { + string errorMessage = string.Format(CultureInfo.InvariantCulture, Modules.WinCompatRequredVersionError, winPSVersionString); + throw new InvalidOperationException(errorMessage); + } + PSSession WindowsPowerShellCompatRemotingSession = CreateWindowsPowerShellCompatResources(); if (WindowsPowerShellCompatRemotingSession == null) { return new List(); } - var moduleProxyList = ImportModule_RemotelyViaPsrpSession(importModuleOptions, moduleNames, moduleFullyQualifiedNames, WindowsPowerShellCompatRemotingSession, usingWinCompat: true); + moduleProxyList = ImportModule_RemotelyViaPsrpSession(importModuleOptions, moduleNames, moduleFullyQualifiedNames, WindowsPowerShellCompatRemotingSession, usingWinCompat: true); foreach(PSModuleInfo moduleProxy in moduleProxyList) { moduleProxy.IsWindowsPowerShellCompatModule = true; @@ -1896,7 +1905,7 @@ namespace Microsoft.PowerShell.Commands string message = StringUtil.Format(Modules.WinCompatModuleWarning, moduleProxy.Name, WindowsPowerShellCompatRemotingSession.Name); WriteWarning(message); } - +#endif return moduleProxyList; } diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index 8afcf5c93c..41a3e4573e 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -446,6 +446,41 @@ namespace System.Management.Automation return null; } + + private static string s_windowsPowerShellVersion = null; + + /// + /// Get the Windows PowerShell version from registry. + /// + /// + /// String of Windows PowerShell version from registry. + /// + internal static string GetWindowsPowerShellVersionFromRegistry() + { + if (!string.IsNullOrEmpty(InternalTestHooks.TestWindowsPowerShellVersionString)) + { + return InternalTestHooks.TestWindowsPowerShellVersionString; + } + + if (s_windowsPowerShellVersion != null) + { + return s_windowsPowerShellVersion; + } + + string engineKeyPath = RegistryStrings.MonadRootKeyPath + "\\" + + PSVersionInfo.RegistryVersionKey + "\\" + RegistryStrings.MonadEngineKey; + + using (RegistryKey engineKey = Registry.LocalMachine.OpenSubKey(engineKeyPath)) + { + if (engineKey != null) + { + s_windowsPowerShellVersion = engineKey.GetValue(RegistryStrings.MonadEngine_MonadVersion) as string; + return s_windowsPowerShellVersion; + } + } + + return string.Empty; + } #endif internal static string DefaultPowerShellAppBase => GetApplicationBase(DefaultPowerShellShellID); @@ -2036,6 +2071,9 @@ namespace System.Management.Automation.Internal // since we can't manipulate the System32 directory in a test internal static string TestWindowsPowerShellPSHomeLocation; + // A version of Windows PS that is installed on the system; normally this is retrieved from a reg key that is write-protected. + internal static string TestWindowsPowerShellVersionString; + internal static bool ShowMarkdownOutputBypass; /// This member is used for internal test purposes. diff --git a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs index b6d6bd6328..ec6b6582f3 100644 --- a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs @@ -1160,8 +1160,9 @@ namespace Microsoft.PowerShell.Commands NewProcessConnectionInfo connectionInfo = new NewProcessConnectionInfo(this.Credential); connectionInfo.AuthenticationMechanism = this.Authentication; +#if !UNIX connectionInfo.PSVersion = new Version(5, 1); - +#endif var typeTable = TypeTable.LoadDefaultTypeFiles(); string runspaceName = GetRunspaceName(0, out int runspaceIdUnused); remoteRunspaces.Add(RunspaceFactory.CreateRunspace(connectionInfo: connectionInfo, diff --git a/src/System.Management.Automation/resources/Modules.resx b/src/System.Management.Automation/resources/Modules.resx index ac44a33e64..973fc5232a 100644 --- a/src/System.Management.Automation/resources/Modules.resx +++ b/src/System.Management.Automation/resources/Modules.resx @@ -513,6 +513,9 @@ 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. + + Detected Windows PowerShell version {0}. Windows PowerShell 5.1 is required to load modules using Windows PowerShell compatibility feature. Install Windows Management Framework (WMF) 5.1 from https://aka.ms/WMF5Download to enable this feature. + The module {0} cannot be imported over a CimSession. Try using the PSSession parameter of the Import-Module cmdlet. 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 58302834fb..b1c08bdcd4 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/CompatiblePSEditions.Module.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/CompatiblePSEditions.Module.Tests.ps1 @@ -246,7 +246,16 @@ Describe "Import-Module from CompatiblePSEditions-checked paths" -Tag "CI" { New-TestModules -TestCases $successCases -BaseDir $basePath New-TestModules -TestCases $failCases -BaseDir $basePath - $allModules = ($successCases + $failCases).ModuleName + $allCases = $successCases + $failCases + $allModules = $allCases.ModuleName + $versionTestCases = @() + foreach($versionString in @('1.0','2.0','3.0','4.0','5.0','5.1','5.1.14393.0')) + { + foreach($case in $allCases) + { + $versionTestCases += $case + @{WinPSVersion = $versionString} + } + } # make sure there are no ImplicitRemoting leftovers from previous tests Get-Module | Where-Object {$_.PrivateData.ImplicitRemoting} | Remove-Module -Force @@ -300,6 +309,24 @@ Describe "Import-Module from CompatiblePSEditions-checked paths" -Tag "CI" { Import-Module $ModuleName -UseWindowsPowerShell -Force & "Test-${ModuleName}PSEdition" | Should -Be 'Desktop' } + + It "WinCompat works only with Windows PS 5.1 (when PSEdition and WinPSVersion )" -TestCases $versionTestCases -Skip:(-not $IsWindows) { + param($Editions, $ModuleName, $Result, $WinPSVersion) + + try { + [System.Management.Automation.Internal.InternalTestHooks]::SetTestHook("TestWindowsPowerShellVersionString", $WinPSVersion) + if ($WinPSVersion.StartsWith('5.1')) { + Import-Module $ModuleName -UseWindowsPowerShell -Force + & "Test-${ModuleName}PSEdition" | Should -Be 'Desktop' + } + else { + { Import-Module $ModuleName -UseWindowsPowerShell -Force } | Should -Throw -ErrorId "InvalidOperationException" + } + } + finally { + [System.Management.Automation.Internal.InternalTestHooks]::SetTestHook("TestWindowsPowerShellVersionString", $null) + } + } } Context "Imports from absolute path" {