From 9b8916fe5a81a5a0ffb902f3ed4568b342535139 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 3 Jan 2022 16:25:15 -0800
Subject: [PATCH 001/209] Bump Microsoft.ApplicationInsights from 2.19.0 to
2.20.0 (#16642)
---
.../System.Management.Automation.csproj | 2 +-
tools/cgmanifest.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj
index bfb49b7998f..1f298d296ab 100644
--- a/src/System.Management.Automation/System.Management.Automation.csproj
+++ b/src/System.Management.Automation/System.Management.Automation.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/tools/cgmanifest.json b/tools/cgmanifest.json
index 3816494e256..15bcbacc172 100644
--- a/tools/cgmanifest.json
+++ b/tools/cgmanifest.json
@@ -35,7 +35,7 @@
"Type": "nuget",
"Nuget": {
"Name": "Microsoft.ApplicationInsights",
- "Version": "2.19.0"
+ "Version": "2.20.0"
}
},
"DevelopmentDependency": false
From 0834c658776b982e5b180e250678cb53efa13730 Mon Sep 17 00:00:00 2001
From: Paul Higinbotham
Date: Tue, 4 Jan 2022 08:30:14 -0800
Subject: [PATCH 002/209] Add AMSI method invocation logging as experimental
feature (#16496)
* Add AMSI method invocation logging as experimental feature
* Add fix for value type errors in logging expression
* Fix recursion error
---
.../ExperimentalFeature.cs | 4 +
.../engine/parser/Compiler.cs | 4 +-
.../engine/runtime/Binding/Binders.cs | 17 ++
.../engine/runtime/Operations/MiscOps.cs | 65 +++++++
.../security/SecuritySupport.cs | 175 +++++++++++++++---
5 files changed, 235 insertions(+), 30 deletions(-)
diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
index 5695d54806d..bfebfd6d2f6 100644
--- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
+++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
@@ -25,6 +25,7 @@ public class ExperimentalFeature
internal const string PSNativeCommandErrorActionPreferenceFeatureName = "PSNativeCommandErrorActionPreference";
internal const string PSRemotingSSHTransportErrorHandling = "PSRemotingSSHTransportErrorHandling";
internal const string PSCleanBlockFeatureName = "PSCleanBlock";
+ internal const string PSAMSIMethodInvocationLogging = "PSAMSIMethodInvocationLogging";
#endregion
@@ -134,6 +135,9 @@ static ExperimentalFeature()
new ExperimentalFeature(
name: PSCleanBlockFeatureName,
description: "Add support of a 'Clean' block to functions and script cmdlets for easy resource cleanup"),
+ new ExperimentalFeature(
+ name: PSAMSIMethodInvocationLogging,
+ description: "Provides AMSI notification of .NET method invocations."),
};
EngineExperimentalFeatures = new ReadOnlyCollection(engineFeatures);
diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs
index 9956fb29e30..b8ca57b4b05 100644
--- a/src/System.Management.Automation/engine/parser/Compiler.cs
+++ b/src/System.Management.Automation/engine/parser/Compiler.cs
@@ -639,7 +639,9 @@ internal static class CachedReflectionInfo
internal static readonly MethodInfo ArgumentTransformationAttribute_Transform =
typeof(ArgumentTransformationAttribute).GetMethod(nameof(ArgumentTransformationAttribute.Transform), InstancePublicFlags);
- // ReSharper restore InconsistentNaming
+
+ internal static readonly MethodInfo MemberInvocationLoggingOps_LogMemberInvocation =
+ typeof(MemberInvocationLoggingOps).GetMethod(nameof(MemberInvocationLoggingOps.LogMemberInvocation), StaticFlags);
}
internal static class ExpressionCache
diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs
index a4917d5af6b..52f85e372b2 100644
--- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs
+++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs
@@ -6903,6 +6903,23 @@ internal static DynamicMetaObject InvokeDotNetMethod(
expr = Expression.Block(expr, ExpressionCache.AutomationNullConstant);
}
+ if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSAMSIMethodInvocationLogging))
+ {
+ // Expression block runs two expressions in order:
+ // - Log method invocation to AMSI Notifications (can throw PSSecurityException)
+ // - Invoke method
+ string targetName = methodInfo.ReflectedType?.FullName ?? string.Empty;
+ expr = Expression.Block(
+ Expression.Call(
+ CachedReflectionInfo.MemberInvocationLoggingOps_LogMemberInvocation,
+ Expression.Constant(targetName),
+ Expression.Constant(name),
+ Expression.NewArrayInit(
+ typeof(object),
+ args.Select(static e => e.Expression.Cast(typeof(object))))),
+ expr);
+ }
+
// If we're calling SteppablePipeline.{Begin|Process|End}, we don't want
// to wrap exceptions - this is very much a special case to help error
// propagation and ensure errors are attributed to the correct code (the
diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs
index 8d50fcf5f1a..62468dcfc3f 100644
--- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs
+++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs
@@ -3546,4 +3546,69 @@ internal static object[] GetSlice(IList list, int startIndex)
return result;
}
}
+
+ internal static class MemberInvocationLoggingOps
+ {
+ private static readonly Lazy DumpLogAMSIContent = new Lazy(
+ () => {
+ object result = Environment.GetEnvironmentVariable("__PSDumpAMSILogContent");
+ if (result != null && LanguagePrimitives.TryConvertTo(result, out int value))
+ {
+ return value == 1;
+ }
+ return false;
+ }
+ );
+
+ internal static void LogMemberInvocation(string targetName, string name, object[] args)
+ {
+ try
+ {
+ var contentName = "PowerShellMemberInvocation";
+ var argsBuilder = new Text.StringBuilder();
+
+ for (int i = 0; i < args.Length; i++)
+ {
+ string value = args[i] is null ? "null" : args[i].ToString();
+
+ if (i > 0)
+ {
+ argsBuilder.Append(", ");
+ }
+
+ argsBuilder.Append($"<{value}>");
+ }
+
+ string content = $"<{targetName}>.{name}({argsBuilder})";
+
+ if (DumpLogAMSIContent.Value)
+ {
+ Console.WriteLine("\n=== Amsi notification report content ===");
+ Console.WriteLine(content);
+ }
+
+ var success = AmsiUtils.ReportContent(
+ name: contentName,
+ content: content);
+
+ if (DumpLogAMSIContent.Value)
+ {
+ Console.WriteLine($"=== Amsi notification report success: {success} ===");
+ }
+ }
+ catch (PSSecurityException)
+ {
+ // ReportContent() will throw PSSecurityException if AMSI detects malware, which
+ // must be propagated.
+ throw;
+ }
+ catch (Exception ex)
+ {
+ if (DumpLogAMSIContent.Value)
+ {
+ Console.WriteLine($"!!! Amsi notification report exception: {ex} !!!");
+ }
+ }
+ }
+ }
}
diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs
index e41b64db625..7b5087411ec 100644
--- a/src/System.Management.Automation/security/SecuritySupport.cs
+++ b/src/System.Management.Automation/security/SecuritySupport.cs
@@ -1383,7 +1383,10 @@ internal static AmsiNativeMethods.AMSI_RESULT ScanContent(string content, string
#endif
}
- internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, string sourceMetadata, bool warmUp)
+ internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(
+ string content,
+ string sourceMetadata,
+ bool warmUp)
{
if (string.IsNullOrEmpty(sourceMetadata))
{
@@ -1414,33 +1417,9 @@ internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, str
try
{
- int hr = 0;
-
- // Initialize AntiMalware Scan Interface, if not already initialized.
- // If we failed to initialize previously, just return the neutral result ("AMSI_RESULT_NOT_DETECTED")
- if (s_amsiContext == IntPtr.Zero)
+ if (!CheckAmsiInit())
{
- hr = Init();
-
- if (!Utils.Succeeded(hr))
- {
- s_amsiInitFailed = true;
- return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED;
- }
- }
-
- // Initialize the session, if one isn't already started.
- // If we failed to initialize previously, just return the neutral result ("AMSI_RESULT_NOT_DETECTED")
- if (s_amsiSession == IntPtr.Zero)
- {
- hr = AmsiNativeMethods.AmsiOpenSession(s_amsiContext, ref s_amsiSession);
- AmsiInitialized = true;
-
- if (!Utils.Succeeded(hr))
- {
- s_amsiInitFailed = true;
- return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED;
- }
+ return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED;
}
if (warmUp)
@@ -1453,6 +1432,7 @@ internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, str
AmsiNativeMethods.AMSI_RESULT result = AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_CLEAN;
// Run AMSI content scan
+ int hr;
unsafe
{
fixed (char* buffer = content)
@@ -1484,6 +1464,123 @@ internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, str
}
}
+ ///
+ /// Reports provided content to AMSI (Antimalware Scan Interface).
+ ///
+ /// Name of content being reported.
+ /// Content being reported.
+ /// True if content was successfully reported.
+ internal static bool ReportContent(
+ string name,
+ string content)
+ {
+#if UNIX
+ return false;
+#else
+ return WinReportContent(name, content);
+#endif
+ }
+
+ private static bool WinReportContent(
+ string name,
+ string content)
+ {
+ if (string.IsNullOrEmpty(name) ||
+ string.IsNullOrEmpty(content) ||
+ s_amsiInitFailed ||
+ s_amsiNotifyFailed)
+ {
+ return false;
+ }
+
+ lock (s_amsiLockObject)
+ {
+ if (s_amsiNotifyFailed)
+ {
+ return false;
+ }
+
+ try
+ {
+ if (!CheckAmsiInit())
+ {
+ return false;
+ }
+
+ int hr;
+ AmsiNativeMethods.AMSI_RESULT result = AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED;
+ unsafe
+ {
+ fixed (char* buffer = content)
+ {
+ var buffPtr = new IntPtr(buffer);
+ hr = AmsiNativeMethods.AmsiNotifyOperation(
+ amsiContext: s_amsiContext,
+ buffer: buffPtr,
+ length: (uint)(content.Length * sizeof(char)),
+ contentName: name,
+ ref result);
+ }
+ }
+
+ if (Utils.Succeeded(hr))
+ {
+ if (result == AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_DETECTED)
+ {
+ // If malware is detected, throw to prevent method invoke expression from running.
+ throw new PSSecurityException(ParserStrings.ScriptContainedMaliciousContent);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+ catch (DllNotFoundException)
+ {
+ s_amsiNotifyFailed = true;
+ return false;
+ }
+ catch (System.EntryPointNotFoundException)
+ {
+ s_amsiNotifyFailed = true;
+ return false;
+ }
+ }
+ }
+
+ private static bool CheckAmsiInit()
+ {
+ // Initialize AntiMalware Scan Interface, if not already initialized.
+ // If we failed to initialize previously, just return the neutral result ("AMSI_RESULT_NOT_DETECTED")
+ if (s_amsiContext == IntPtr.Zero)
+ {
+ int hr = Init();
+
+ if (!Utils.Succeeded(hr))
+ {
+ s_amsiInitFailed = true;
+ return false;
+ }
+ }
+
+ // Initialize the session, if one isn't already started.
+ // If we failed to initialize previously, just return the neutral result ("AMSI_RESULT_NOT_DETECTED")
+ if (s_amsiSession == IntPtr.Zero)
+ {
+ int hr = AmsiNativeMethods.AmsiOpenSession(s_amsiContext, ref s_amsiSession);
+ AmsiInitialized = true;
+
+ if (!Utils.Succeeded(hr))
+ {
+ s_amsiInitFailed = true;
+ return false;
+ }
+ }
+
+ return true;
+ }
+
internal static void CurrentDomain_ProcessExit(object sender, EventArgs e)
{
if (AmsiInitialized && !AmsiUninitializeCalled)
@@ -1499,6 +1596,7 @@ internal static void CurrentDomain_ProcessExit(object sender, EventArgs e)
private static IntPtr s_amsiSession = IntPtr.Zero;
private static bool s_amsiInitFailed = false;
+ private static bool s_amsiNotifyFailed = false;
private static readonly object s_amsiLockObject = new object();
///
@@ -1623,8 +1721,27 @@ internal static extern int AmsiInitialize(
[DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)]
[DllImportAttribute("amsi.dll", EntryPoint = "AmsiScanBuffer", CallingConvention = CallingConvention.StdCall)]
internal static extern int AmsiScanBuffer(
- System.IntPtr amsiContext, System.IntPtr buffer, uint length,
- [InAttribute()][MarshalAsAttribute(UnmanagedType.LPWStr)] string contentName, System.IntPtr amsiSession, ref AMSI_RESULT result);
+ System.IntPtr amsiContext,
+ System.IntPtr buffer,
+ uint length,
+ [InAttribute()][MarshalAsAttribute(UnmanagedType.LPWStr)] string contentName,
+ System.IntPtr amsiSession,
+ ref AMSI_RESULT result);
+
+ /// Return Type: HRESULT->LONG->int
+ /// amsiContext: HAMSICONTEXT->HAMSICONTEXT__*
+ /// buffer: PVOID->void*
+ /// length: ULONG->unsigned int
+ /// contentName: LPCWSTR->WCHAR*
+ /// result: AMSI_RESULT*
+ [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)]
+ [DllImportAttribute("amsi.dll", EntryPoint = "AmsiNotifyOperation", CallingConvention = CallingConvention.StdCall)]
+ internal static extern int AmsiNotifyOperation(
+ System.IntPtr amsiContext,
+ System.IntPtr buffer,
+ uint length,
+ [InAttribute()][MarshalAsAttribute(UnmanagedType.LPWStr)] string contentName,
+ ref AMSI_RESULT result);
/// Return Type: HRESULT->LONG->int
///amsiContext: HAMSICONTEXT->HAMSICONTEXT__*
From a9b14a13fd9349e4df61cba7b46d47fc98f831ee Mon Sep 17 00:00:00 2001
From: Steve Lee
Date: Tue, 4 Jan 2022 14:09:27 -0800
Subject: [PATCH 003/209] Add `exec` cmdlet for bash compatibility (#16462)
---
.../ExperimentalFeature.cs | 4 +
.../engine/InitialSessionState.cs | 18 ++-
.../engine/Modules/SwitchProcessCommand.cs | 115 ++++++++++++++++++
.../resources/CommandBaseStrings.resx | 6 +
.../Microsoft.PowerShell.Core/Exec.Tests.ps1 | 74 +++++++++++
.../engine/Help/HelpSystem.Tests.ps1 | 3 +-
6 files changed, 218 insertions(+), 2 deletions(-)
create mode 100644 src/System.Management.Automation/engine/Modules/SwitchProcessCommand.cs
create mode 100644 test/powershell/Modules/Microsoft.PowerShell.Core/Exec.Tests.ps1
diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
index bfebfd6d2f6..d23f5b63290 100644
--- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
+++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
@@ -26,6 +26,7 @@ public class ExperimentalFeature
internal const string PSRemotingSSHTransportErrorHandling = "PSRemotingSSHTransportErrorHandling";
internal const string PSCleanBlockFeatureName = "PSCleanBlock";
internal const string PSAMSIMethodInvocationLogging = "PSAMSIMethodInvocationLogging";
+ internal const string PSExecFeatureName = "PSExec";
#endregion
@@ -138,6 +139,9 @@ static ExperimentalFeature()
new ExperimentalFeature(
name: PSAMSIMethodInvocationLogging,
description: "Provides AMSI notification of .NET method invocations."),
+ new ExperimentalFeature(
+ name: PSExecFeatureName,
+ description: "Add 'exec' built-in command on Linux and macOS"),
};
EngineExperimentalFeatures = new ReadOnlyCollection(engineFeatures);
diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs
index 05eb871053d..ce0fff99f46 100644
--- a/src/System.Management.Automation/engine/InitialSessionState.cs
+++ b/src/System.Management.Automation/engine/InitialSessionState.cs
@@ -4634,7 +4634,7 @@ internal static SessionStateAliasEntry[] BuiltInAliases
const ScopedItemOptions ReadOnly_AllScope = ScopedItemOptions.ReadOnly | ScopedItemOptions.AllScope;
const ScopedItemOptions ReadOnly = ScopedItemOptions.ReadOnly;
- return new SessionStateAliasEntry[] {
+ var builtInAliases = new List {
new SessionStateAliasEntry("foreach", "ForEach-Object", string.Empty, ReadOnly_AllScope),
new SessionStateAliasEntry("%", "ForEach-Object", string.Empty, ReadOnly_AllScope),
new SessionStateAliasEntry("where", "Where-Object", string.Empty, ReadOnly_AllScope),
@@ -4801,6 +4801,15 @@ internal static SessionStateAliasEntry[] BuiltInAliases
// - do not use AllScope - this causes errors in profiles that set this somewhat commonly used alias.
new SessionStateAliasEntry("sls", "Select-String"),
};
+
+#if UNIX
+ if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSExecFeatureName))
+ {
+ builtInAliases.Add(new SessionStateAliasEntry("exec", "Switch-Process"));
+ }
+#endif
+
+ return builtInAliases.ToArray();
}
}
@@ -5439,6 +5448,13 @@ private static void InitializeCoreCmdletsAndProviders(
cmdlets.Add("Get-PSSubsystem", new SessionStateCmdletEntry("Get-PSSubsystem", typeof(Subsystem.GetPSSubsystemCommand), helpFile));
}
+#if UNIX
+ if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSExecFeatureName))
+ {
+ cmdlets.Add("Switch-Process", new SessionStateCmdletEntry("Switch-Process", typeof(SwitchProcessCommand), helpFile));
+ }
+#endif
+
foreach (var val in cmdlets.Values)
{
val.SetPSSnapIn(psSnapInInfo);
diff --git a/src/System.Management.Automation/engine/Modules/SwitchProcessCommand.cs b/src/System.Management.Automation/engine/Modules/SwitchProcessCommand.cs
new file mode 100644
index 00000000000..298587679b1
--- /dev/null
+++ b/src/System.Management.Automation/engine/Modules/SwitchProcessCommand.cs
@@ -0,0 +1,115 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Management.Automation;
+using System.Runtime.InteropServices;
+
+using Dbg = System.Management.Automation.Diagnostics;
+
+#if UNIX
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// Implements a cmdlet that allows use of execv API.
+ ///
+ [Experimental(ExperimentalFeature.PSExecFeatureName, ExperimentAction.Show)]
+ [Cmdlet(VerbsCommon.Switch, "Process", HelpUri = "https://go.microsoft.com/fwlink/?linkid=2181448")]
+ public sealed class SwitchProcessCommand : PSCmdlet
+ {
+ ///
+ /// Get or set the command and arguments to replace the current pwsh process.
+ ///
+ [Parameter(Position = 0, Mandatory = false, ValueFromRemainingArguments = true)]
+ public string[] WithCommand { get; set; } = Array.Empty();
+
+ ///
+ /// Execute the command and arguments
+ ///
+ protected override void EndProcessing()
+ {
+ if (WithCommand.Length == 0)
+ {
+ return;
+ }
+
+ // execv requires command to be full path so resolve command to first match
+ var command = this.SessionState.InvokeCommand.GetCommand(WithCommand[0], CommandTypes.Application);
+ if (command is null)
+ {
+ ThrowTerminatingError(
+ new ErrorRecord(
+ new CommandNotFoundException(
+ string.Format(
+ System.Globalization.CultureInfo.InvariantCulture,
+ CommandBaseStrings.NativeCommandNotFound,
+ command
+ )
+ ),
+ "CommandNotFound",
+ ErrorCategory.InvalidArgument,
+ WithCommand
+ )
+ );
+ }
+
+ var execArgs = new string?[WithCommand.Length + 1];
+
+ // execv convention is the first arg is the program name
+ execArgs[0] = command.Name;
+
+ for (int i = 1; i < WithCommand.Length; i++)
+ {
+ execArgs[i] = WithCommand[i];
+ }
+
+ // need null terminator at end
+ execArgs[execArgs.Length - 1] = null;
+
+ int exitCode = Exec(command.Source, execArgs);
+
+ if (exitCode < 0)
+ {
+ ThrowTerminatingError(
+ new ErrorRecord(
+ new Exception(
+ string.Format(
+ System.Globalization.CultureInfo.InvariantCulture,
+ CommandBaseStrings.ExecFailed,
+ Marshal.GetLastPInvokeError(),
+ string.Join(" ", WithCommand)
+ )
+ ),
+ "ExecutionFailed",
+ ErrorCategory.InvalidOperation,
+ WithCommand
+ )
+ );
+ }
+ }
+
+ ///
+ /// The `execv` POSIX syscall we use to exec /bin/sh.
+ ///
+ /// The path to the executable to exec.
+ ///
+ /// The arguments to send through to the executable.
+ /// Array must have its final element be null.
+ ///
+ ///
+ /// An exit code if exec failed, but if successful the calling process will be overwritten.
+ ///
+ [DllImport("libc",
+ EntryPoint = "execv",
+ CallingConvention = CallingConvention.Cdecl,
+ CharSet = CharSet.Ansi,
+ SetLastError = true)]
+ private static extern int Exec(string path, string?[] args);
+ }
+}
+
+#endif
diff --git a/src/System.Management.Automation/resources/CommandBaseStrings.resx b/src/System.Management.Automation/resources/CommandBaseStrings.resx
index dc2dd4bbbf5..abb918adb16 100644
--- a/src/System.Management.Automation/resources/CommandBaseStrings.resx
+++ b/src/System.Management.Automation/resources/CommandBaseStrings.resx
@@ -222,4 +222,10 @@ Reviewed by TArcher on 2010-07-20
The {0} is obsolete. {1}
+
+ Exec call failed with errorno {0} for command line: {1}
+
+
+ Command '{0}' was not found. The specified command must be an executable.
+
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Exec.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Exec.Tests.ps1
new file mode 100644
index 00000000000..bd3c87e0f5c
--- /dev/null
+++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Exec.Tests.ps1
@@ -0,0 +1,74 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+Describe 'Switch-Process tests for Unix' -Tags 'CI' {
+ BeforeAll {
+ $originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
+ if (-not [ExperimentalFeature]::IsEnabled('PSExec') -or $IsWindows)
+ {
+ $PSDefaultParameterValues['It:Skip'] = $true
+ return
+ }
+ }
+
+ AfterAll {
+ $global:PSDefaultParameterValues = $originalDefaultParameterValues
+ }
+
+ It 'Exec alias should map to Switch-Process' {
+ $alias = Get-Command exec
+ $alias | Should -BeOfType [System.Management.Automation.AliasInfo]
+ $alias.Definition | Should -BeExactly 'Switch-Process'
+ }
+
+ It 'Exec by itself does nothing' {
+ exec | Should -BeNullOrEmpty
+ }
+
+ It 'Exec given a cmdlet should fail' {
+ { exec Get-Command } | Should -Throw -ErrorId 'CommandNotFound,Microsoft.PowerShell.Commands.SwitchProcessCommand'
+ }
+
+ It 'Exec given an exe should work' {
+ $id, $uname = pwsh -noprofile -noexit -outputformat text -command { $pid; exec uname }
+ Get-Process -Id $id -ErrorAction Ignore| Should -BeNullOrEmpty
+ $uname | Should -BeExactly (uname)
+ }
+
+ It 'Exec given an exe and arguments should work' {
+ $id, $uname = pwsh -noprofile -noexit -outputformat text -command { $pid; exec uname -a }
+ Get-Process -Id $id -ErrorAction Ignore| Should -BeNullOrEmpty
+ $uname | Should -BeExactly (uname -a)
+ }
+
+ It 'Exec will replace the process' {
+ $sleep = Get-Command sleep -CommandType Application | Select-Object -First 1
+ $p = Start-Process pwsh -ArgumentList "-noprofile -command exec $($sleep.Source) 90" -PassThru
+ Wait-UntilTrue {
+ ($p | Get-Process).Name -eq 'sleep'
+ } -timeout 60000 -interval 100 | Should -BeTrue
+ }
+}
+
+Describe 'Switch-Process for Windows' {
+ BeforeAll {
+ $originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
+ if (-not $IsWindows)
+ {
+ $PSDefaultParameterValues['It:Skip'] = $true
+ return
+ }
+ }
+
+ AfterAll {
+ $global:PSDefaultParameterValues = $originalDefaultParameterValues
+ }
+
+ It 'Switch-Process should not be available' {
+ Get-Command -Name Switch-Process -ErrorAction Ignore | Should -BeNullOrEmpty
+ }
+
+ It 'Exec alias should not be available' {
+ Get-Alias -Name exec -ErrorAction Ignore | Should -BeNullOrEmpty
+ }
+}
diff --git a/test/powershell/engine/Help/HelpSystem.Tests.ps1 b/test/powershell/engine/Help/HelpSystem.Tests.ps1
index 3fbd0822352..0590c7877ff 100644
--- a/test/powershell/engine/Help/HelpSystem.Tests.ps1
+++ b/test/powershell/engine/Help/HelpSystem.Tests.ps1
@@ -16,7 +16,8 @@ $script:cmdletsToSkip = @(
"Get-ExperimentalFeature",
"Enable-ExperimentalFeature",
"Disable-ExperimentalFeature",
- "Get-PSSubsystem"
+ "Get-PSSubsystem",
+ "Switch-Process"
)
function UpdateHelpFromLocalContentPath {
From 2e8009bf721f3997bbcf710a8c683f94778f97c5 Mon Sep 17 00:00:00 2001
From: Steve Lee
Date: Tue, 4 Jan 2022 16:16:53 -0800
Subject: [PATCH 004/209] Change nuget release yaml to use `UseDotNet` task
(#16701)
---
tools/releaseBuild/azureDevOps/templates/nuget.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tools/releaseBuild/azureDevOps/templates/nuget.yml b/tools/releaseBuild/azureDevOps/templates/nuget.yml
index 2f583b52c4a..b868c9dbdf7 100644
--- a/tools/releaseBuild/azureDevOps/templates/nuget.yml
+++ b/tools/releaseBuild/azureDevOps/templates/nuget.yml
@@ -51,10 +51,10 @@ jobs:
Write-Host "##$vstsCommandString"
displayName: 'Find SDK version from global.json'
- - task: DotNetCoreInstaller@0
+ - task: UseDotNet@2
displayName: 'Use .NET Core SDK from global.json'
inputs:
- version: '$(SDKVersion)'
+ useGlobalJson: true
- task: DownloadBuildArtifacts@0
displayName: 'Download PowerShell build artifacts - finalResults'
From e2f39fa0eb29af4c606dc06b77046370d94a91e8 Mon Sep 17 00:00:00 2001
From: Keith Hill
Date: Mon, 10 Jan 2022 16:08:00 -0700
Subject: [PATCH 005/209] Reduce the amount of startup banner text (#16516)
---
.../host/msh/CommandLineParameterParser.cs | 1 -
.../host/msh/UpdatesNotification.cs | 1 +
.../resources/ManagedEntranceStrings.resx | 8 +--
test/powershell/Host/ConsoleHost.Tests.ps1 | 58 +++++++++++++++++++
4 files changed, 61 insertions(+), 7 deletions(-)
diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs
index 6908320db53..795921a0c5a 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs
@@ -690,7 +690,6 @@ private void DisplayBanner(PSHostUserInterface hostUI, string? bannerText)
if (!string.IsNullOrEmpty(bannerText))
{
hostUI.WriteLine(bannerText);
- hostUI.WriteLine();
}
if (UpdatesNotification.CanNotifyUpdates)
diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs
index e9bc2d343b4..455efe84d11 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs
@@ -126,6 +126,7 @@ internal static void ShowUpdateNotification(PSHostUserInterface hostUI)
string notificationMsg = string.Format(CultureInfo.CurrentCulture, notificationMsgTemplate, releaseTag, notificationColor, resetColor, line2Padding, line3Padding);
+ hostUI.WriteLine();
hostUI.WriteLine(notificationMsg);
}
}
diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx
index 7b13837b294..481a4216cf9 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx
+++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx
@@ -118,11 +118,7 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
- PowerShell {0}
-Copyright (c) Microsoft Corporation.
-
-https://aka.ms/powershell
-Type 'help' to get help.
+ PowerShell {0}
Warning: PowerShell detected that you might be using a screen reader and has disabled PSReadLine for compatibility purposes. If you want to re-enable it, run 'Import-Module PSReadLine'.
@@ -383,7 +379,7 @@ All parameters are case-insensitive.
-NoLogo | -nol
- Hides the copyright banner at startup of interactive sessions.
+ Hides the banner text at startup of interactive sessions.
-NonInteractive | -noni
diff --git a/test/powershell/Host/ConsoleHost.Tests.ps1 b/test/powershell/Host/ConsoleHost.Tests.ps1
index 06f8f0a8198..1798a71939f 100644
--- a/test/powershell/Host/ConsoleHost.Tests.ps1
+++ b/test/powershell/Host/ConsoleHost.Tests.ps1
@@ -857,6 +857,64 @@ $powershell -c '[System.Management.Automation.Platform]::SelectProductNameForDir
$LASTEXITCODE | Should -Be $ExitCodeBadCommandLineParameter
}
}
+
+ Context "Startup banner text tests" -Tag Slow {
+ BeforeAll {
+ $outputPath = "Temp:\StartupBannerTest-Output-${Pid}.txt"
+ $inputPath = "Temp:\StartupBannerTest-Input.txt"
+ "exit" > $inputPath
+
+ # Not testing update notification banner text here
+ $oldPowerShellUpdateCheck = $env:POWERSHELL_UPDATECHECK
+ $env:POWERSHELL_UPDATECHECK = "Off"
+
+ # Set TERM to "dumb" to avoid DECCKM codes in the output
+ $oldTERM = $env:TERM
+ $env:TERM = "dumb"
+
+ $escPwd = [regex]::Escape($pwd)
+ $expectedPromptPattern = "^PS ${escPwd}> exit`$"
+
+ $spArgs = @{
+ FilePath = $powershell
+ ArgumentList = @("-NoProfile")
+ RedirectStandardInput = $inputPath
+ RedirectStandardOutput = $outputPath
+ WorkingDirectory = $pwd
+ PassThru = $true
+ NoNewWindow = $true
+ UseNewEnvironment = $false
+ }
+ }
+ AfterAll {
+ $env:TERM = $oldTERM
+ $env:POWERSHELL_UPDATECHECK = $oldPowerShellUpdateCheck
+
+ Remove-Item $inputPath -Force -ErrorAction Ignore
+ Remove-Item $outputPath -Force -ErrorAction Ignore
+ }
+ BeforeEach {
+ Remove-Item $outputPath -Force -ErrorAction Ignore
+ }
+ It "Displays expected startup banner text by default" {
+ $process = Start-Process @spArgs
+ Wait-UntilTrue -sb { $process.HasExited } -TimeoutInMilliseconds 5000 -IntervalInMilliseconds 250 | Should -BeTrue
+
+ $out = @(Get-Content $outputPath)
+ $out.Count | Should -Be 2
+ $out[0] | Should -BeExactly "PowerShell $($PSVersionTable.GitCommitId)"
+ $out[1] | Should -MatchExactly $expectedPromptPattern
+ }
+ It "Displays only the prompt with -NoLogo" {
+ $spArgs["ArgumentList"] += "-NoLogo"
+ $process = Start-Process @spArgs
+ Wait-UntilTrue -sb { $process.HasExited } -TimeoutInMilliseconds 5000 -IntervalInMilliseconds 250 | Should -BeTrue
+
+ $out = @(Get-Content $outputPath)
+ $out.Count | Should -Be 1
+ $out[0] | Should -MatchExactly $expectedPromptPattern
+ }
+ }
}
Describe "WindowStyle argument" -Tag Feature {
From 0f21156dee50c28738cd9a71086458ba651e2d28 Mon Sep 17 00:00:00 2001
From: Steve Lee
Date: Tue, 11 Jan 2022 23:21:32 -0800
Subject: [PATCH 006/209] Fix `$PSNativeCommandArgPassing` = `Windows` to
handle empty args correctly (#16639)
---
.../engine/NativeCommandParameterBinder.cs | 4 ++--
.../NativeCommandArguments.Tests.ps1 | 24 ++++++++++++++++++-
2 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs
index 59715a02125..200cfeee2ef 100644
--- a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs
+++ b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs
@@ -314,7 +314,7 @@ private void AppendOneNativeArgument(ExecutionContext context, CommandParameterI
}
else
{
- if (argArrayAst != null && ArgumentPassingStyle == NativeArgumentPassingStyle.Standard)
+ if (argArrayAst != null && ArgumentPassingStyle != NativeArgumentPassingStyle.Legacy)
{
// We have a literal array, so take the extent, break it on spaces and add them to the argument list.
foreach (string element in argArrayAst.Extent.Text.Split(' ', StringSplitOptions.RemoveEmptyEntries))
@@ -331,7 +331,7 @@ private void AppendOneNativeArgument(ExecutionContext context, CommandParameterI
}
}
}
- else if (ArgumentPassingStyle == NativeArgumentPassingStyle.Standard && currentObj != null)
+ else if (ArgumentPassingStyle != NativeArgumentPassingStyle.Legacy && currentObj != null)
{
// add empty strings to arglist, but not nulls
AddToArgumentList(parameter, arg);
diff --git a/test/powershell/Language/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1
index 0c7b7f9d512..c8e3c75cb17 100644
--- a/test/powershell/Language/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1
+++ b/test/powershell/Language/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1
@@ -171,7 +171,7 @@ Describe "Will error correctly if an attempt to set variable to improper value"
}
}
-foreach ( $argumentListValue in "Standard","Legacy" ) {
+foreach ( $argumentListValue in "Standard","Legacy","Windows" ) {
$PSNativeCommandArgumentPassing = $argumentListValue
Describe "Native Command Arguments (${PSNativeCommandArgumentPassing})" -tags "CI" {
# When passing arguments to native commands, quoted segments that contain
@@ -263,6 +263,28 @@ foreach ( $argumentListValue in "Standard","Legacy" ) {
$lines[$i] | Should -BeExactly "Arg $i is <$($expected[$i])>"
}
}
+
+ It "Should handle empty args correctly (ArgumentList=${PSNativeCommandArgumentPassing})" {
+ if ($PSNativeCommandArgumentPassing -eq 'Legacy') {
+ $expectedLines = 2
+ }
+ else {
+ $expectedLines = 3
+ }
+
+ $lines = testexe -echoargs 1 '' 2
+ $lines.Count | Should -Be $expectedLines
+ $lines[0] | Should -BeExactly 'Arg 0 is <1>'
+
+ if ($expectedLines -eq 2) {
+ $lines[1] | Should -BeExactly 'Arg 1 is <2>'
+ }
+ else {
+ $lines[1] | Should -BeExactly 'Arg 1 is <>'
+ $lines[2] | Should -BeExactly 'Arg 2 is <2>'
+ }
+
+ }
}
}
Describe 'PSPath to native commands' -tags "CI" {
From 1cb35533494412f54483b2add8321605151cff19 Mon Sep 17 00:00:00 2001
From: Dongbo Wang
Date: Wed, 12 Jan 2022 00:29:35 -0800
Subject: [PATCH 007/209] Update change log to remove a broken URL (#16735)
---
CHANGELOG/6.0.md | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG/6.0.md b/CHANGELOG/6.0.md
index 948197f0619..52db53afabf 100644
--- a/CHANGELOG/6.0.md
+++ b/CHANGELOG/6.0.md
@@ -766,9 +766,8 @@ For more information on this, we invite you to read [this blog post explaining P
### Move to .NET Core 2.0 (.NET Standard 2.0 support)
PowerShell Core has moved to using .NET Core 2.0 so that we can leverage all the benefits of .NET Standard 2.0. (#3556)
-To learn more about .NET Standard 2.0, there's some great starter content [on Youtube](https://www.youtube.com/playlist?list=PLRAdsfhKI4OWx321A_pr-7HhRNk7wOLLY),
-on [the .NET blog](https://devblogs.microsoft.com/dotnet/introducing-net-standard/),
-and [on GitHub](https://github.com/dotnet/standard/blob/master/docs/faq.md).
+To learn more about .NET Standard 2.0, there's some great starter content [on Youtube](https://www.youtube.com/playlist?list=PLRAdsfhKI4OWx321A_pr-7HhRNk7wOLLY)
+and on [the .NET blog](https://devblogs.microsoft.com/dotnet/introducing-net-standard/).
We'll also have more content soon in our [repository documentation](https://github.com/PowerShell/PowerShell/tree/master/docs) (which will eventually make its way to [official documentation](https://github.com/powershell/powershell-docs)).
In a nutshell, .NET Standard 2.0 allows us to have universal, portable modules between Windows PowerShell (which uses the full .NET Framework) and PowerShell Core (which uses .NET Core).
Many modules and cmdlets that didn't work in the past may now work on .NET Core, so import your favorite modules and tell us what does and doesn't work in our GitHub Issues!
From cce64f49deffaf4aabd968b9242d2ba98ff3031f Mon Sep 17 00:00:00 2001
From: Dongbo Wang
Date: Wed, 12 Jan 2022 10:02:03 -0800
Subject: [PATCH 008/209] Report error when PowerShell built-in modules are
missing (#16628)
---
.../engine/CommandDiscovery.cs | 55 ++--
.../engine/InitialSessionState.cs | 65 +----
.../engine/Modules/ImportModuleCommand.cs | 241 +++++++++++-------
.../engine/Modules/ModuleCmdletBase.cs | 98 +++----
.../engine/Modules/ModuleIntrinsics.cs | 21 +-
.../engine/Modules/ModuleUtils.cs | 2 +-
.../engine/remoting/client/remoterunspace.cs | 8 -
.../resources/DiscoveryExceptions.resx | 4 +
.../resources/Modules.resx | 3 +
.../CompatiblePSEditions.Module.Tests.ps1 | 169 +++++++++++-
10 files changed, 426 insertions(+), 240 deletions(-)
diff --git a/src/System.Management.Automation/engine/CommandDiscovery.cs b/src/System.Management.Automation/engine/CommandDiscovery.cs
index 6d7fb77eba8..5de18cdc952 100644
--- a/src/System.Management.Automation/engine/CommandDiscovery.cs
+++ b/src/System.Management.Automation/engine/CommandDiscovery.cs
@@ -376,22 +376,16 @@ private static void VerifyRequiredSnapins(IEnumerable req
foreach (var requiresPSSnapIn in requiresPSSnapIns)
{
- IEnumerable loadedPSSnapIns = null;
- loadedPSSnapIns = context.InitialSessionState.GetPSSnapIn(requiresPSSnapIn.Name);
- if (loadedPSSnapIns == null || !loadedPSSnapIns.Any())
+ var loadedPSSnapIn = context.InitialSessionState.GetPSSnapIn(requiresPSSnapIn.Name);
+ if (loadedPSSnapIn is null)
{
- if (requiresMissingPSSnapIns == null)
- {
- requiresMissingPSSnapIns = new Collection();
- }
-
+ requiresMissingPSSnapIns ??= new Collection();
requiresMissingPSSnapIns.Add(BuildPSSnapInDisplayName(requiresPSSnapIn));
}
else
{
// the requires PSSnapin is loaded. now check the PSSnapin version
- PSSnapInInfo loadedPSSnapIn = loadedPSSnapIns.First();
- Diagnostics.Assert(loadedPSSnapIn.Version != null,
+ Dbg.Assert(loadedPSSnapIn.Version != null,
string.Format(
CultureInfo.InvariantCulture,
"Version is null for loaded PSSnapin {0}.", loadedPSSnapIn));
@@ -400,11 +394,7 @@ private static void VerifyRequiredSnapins(IEnumerable req
if (!AreInstalledRequiresVersionsCompatible(
requiresPSSnapIn.Version, loadedPSSnapIn.Version))
{
- if (requiresMissingPSSnapIns == null)
- {
- requiresMissingPSSnapIns = new Collection();
- }
-
+ requiresMissingPSSnapIns ??= new Collection();
requiresMissingPSSnapIns.Add(BuildPSSnapInDisplayName(requiresPSSnapIn));
}
}
@@ -1063,14 +1053,12 @@ private static CommandInfo TryModuleAutoDiscovery(string commandName,
return null;
CmdletInfo cmdletInfo = context.SessionState.InvokeCommand.GetCmdlet("Microsoft.PowerShell.Core\\Get-Module");
- if ((commandOrigin == CommandOrigin.Internal) ||
- ((cmdletInfo != null) && (cmdletInfo.Visibility == SessionStateEntryVisibility.Public)))
+ if (commandOrigin == CommandOrigin.Internal || cmdletInfo?.Visibility == SessionStateEntryVisibility.Public)
{
// Search for a module with a matching command, as long as the user would have the ability to
// import the module.
cmdletInfo = context.SessionState.InvokeCommand.GetCmdlet("Microsoft.PowerShell.Core\\Import-Module");
- if (((commandOrigin == CommandOrigin.Internal) ||
- ((cmdletInfo != null) && (cmdletInfo.Visibility == SessionStateEntryVisibility.Public))))
+ if (commandOrigin == CommandOrigin.Internal || cmdletInfo?.Visibility == SessionStateEntryVisibility.Public)
{
discoveryTracer.WriteLine("Executing non module-qualified search: {0}", commandName);
context.CommandDiscovery.RegisterLookupCommandInfoAction("ActiveModuleSearch", commandName);
@@ -1085,30 +1073,33 @@ private static CommandInfo TryModuleAutoDiscovery(string commandName,
{
// WinBlue:69141 - We need to get the full path here because the module path might be C:\Users\User1\DOCUME~1
// While the exportedCommands are cached, they are cached with the full path
- string expandedModulePath = IO.Path.GetFullPath(modulePath);
- string moduleShortName = System.IO.Path.GetFileNameWithoutExtension(expandedModulePath);
+ string expandedModulePath = Path.GetFullPath(modulePath);
+ string moduleShortName = Path.GetFileNameWithoutExtension(expandedModulePath);
var exportedCommands = AnalysisCache.GetExportedCommands(expandedModulePath, false, context);
if (exportedCommands == null) { continue; }
- CommandTypes exportedCommandTypes;
// Skip if module only has class or other types and no commands.
- if (exportedCommands.TryGetValue(commandName, out exportedCommandTypes))
+ if (exportedCommands.TryGetValue(commandName, out CommandTypes exportedCommandTypes))
{
- Exception exception;
discoveryTracer.WriteLine("Found in module: {0}", expandedModulePath);
- Collection matchingModule = AutoloadSpecifiedModule(expandedModulePath, context,
+ Collection matchingModule = AutoloadSpecifiedModule(
+ expandedModulePath,
+ context,
cmdletInfo != null ? cmdletInfo.Visibility : SessionStateEntryVisibility.Private,
- out exception);
- lastError = exception;
- if ((matchingModule == null) || (matchingModule.Count == 0))
+ out lastError);
+
+ if (matchingModule is null || matchingModule.Count == 0)
{
- string error = StringUtil.Format(DiscoveryExceptions.CouldNotAutoImportMatchingModule, commandName, moduleShortName);
- CommandNotFoundException commandNotFound = new CommandNotFoundException(
+ string errorMessage = lastError is null
+ ? StringUtil.Format(DiscoveryExceptions.CouldNotAutoImportMatchingModule, commandName, moduleShortName)
+ : StringUtil.Format(DiscoveryExceptions.CouldNotAutoImportMatchingModuleWithErrorMessage, commandName, moduleShortName, lastError.Message);
+
+ throw new CommandNotFoundException(
originalCommandName,
lastError,
- "CouldNotAutoloadMatchingModule", error);
- throw commandNotFound;
+ "CouldNotAutoloadMatchingModule",
+ errorMessage);
}
result = LookupCommandInfo(commandName, commandTypes, searchResolutionOptions, commandOrigin, context);
diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs
index ce0fff99f46..bc40e903fa4 100644
--- a/src/System.Management.Automation/engine/InitialSessionState.cs
+++ b/src/System.Management.Automation/engine/InitialSessionState.cs
@@ -1663,11 +1663,6 @@ public InitialSessionState Clone()
ss.DisableFormatUpdates = this.DisableFormatUpdates;
- foreach (var s in this.defaultSnapins)
- {
- ss.defaultSnapins.Add(s);
- }
-
foreach (var s in ImportedSnapins)
{
ss.ImportedSnapins.Add(s.Key, s.Value);
@@ -3801,34 +3796,26 @@ public PSSnapInInfo ImportPSSnapIn(string name, out PSSnapInException warning)
// Now actually load the snapin...
PSSnapInInfo snapin = ImportPSSnapIn(newPSSnapIn, out warning);
- if (snapin != null)
- {
- ImportedSnapins.Add(snapin.Name, snapin);
- }
return snapin;
}
internal PSSnapInInfo ImportCorePSSnapIn()
{
- // Load Microsoft.PowerShell.Core as a snapin
+ // Load Microsoft.PowerShell.Core as a snapin.
PSSnapInInfo coreSnapin = PSSnapInReader.ReadCoreEngineSnapIn();
- this.defaultSnapins.Add(coreSnapin);
- try
- {
- PSSnapInException warning;
- this.ImportPSSnapIn(coreSnapin, out warning);
- }
- catch (PSSnapInException)
- {
- throw;
- }
-
+ ImportPSSnapIn(coreSnapin, out _);
return coreSnapin;
}
internal PSSnapInInfo ImportPSSnapIn(PSSnapInInfo psSnapInInfo, out PSSnapInException warning)
{
+ if (psSnapInInfo == null)
+ {
+ ArgumentNullException e = new ArgumentNullException(nameof(psSnapInInfo));
+ throw e;
+ }
+
// See if the snapin is already loaded. If has been then there will be an entry in the
// Assemblies list for it already...
bool reload = true;
@@ -3861,12 +3848,6 @@ internal PSSnapInInfo ImportPSSnapIn(PSSnapInInfo psSnapInInfo, out PSSnapInExce
Dictionary> aliases = null;
Dictionary providers = null;
- if (psSnapInInfo == null)
- {
- ArgumentNullException e = new ArgumentNullException(nameof(psSnapInInfo));
- throw e;
- }
-
Assembly assembly = null;
string helpFile = null;
@@ -3985,37 +3966,18 @@ internal PSSnapInInfo ImportPSSnapIn(PSSnapInInfo psSnapInInfo, out PSSnapInExce
}
}
+ ImportedSnapins.Add(psSnapInInfo.Name, psSnapInInfo);
return psSnapInInfo;
}
- internal List GetPSSnapIn(string psSnapinName)
+ internal PSSnapInInfo GetPSSnapIn(string psSnapinName)
{
- List loadedSnapins = null;
- foreach (var defaultSnapin in defaultSnapins)
- {
- if (defaultSnapin.Name.Equals(psSnapinName, StringComparison.OrdinalIgnoreCase))
- {
- if (loadedSnapins == null)
- {
- loadedSnapins = new List();
- }
-
- loadedSnapins.Add(defaultSnapin);
- }
- }
-
- PSSnapInInfo importedSnapin = null;
- if (ImportedSnapins.TryGetValue(psSnapinName, out importedSnapin))
+ if (ImportedSnapins.TryGetValue(psSnapinName, out PSSnapInInfo importedSnapin))
{
- if (loadedSnapins == null)
- {
- loadedSnapins = new List();
- }
-
- loadedSnapins.Add(importedSnapin);
+ return importedSnapin;
}
- return loadedSnapins;
+ return null;
}
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")]
@@ -4895,7 +4857,6 @@ internal static void RemoveAllDrivesForProvider(ProviderInfo pi, SessionStateInt
internal static readonly string CoreSnapin = "Microsoft.PowerShell.Core";
internal static readonly string CoreModule = "Microsoft.PowerShell.Core";
- internal Collection defaultSnapins = new Collection();
// The list of engine modules to create warnings when you try to remove them
internal static readonly HashSet EngineModules = new HashSet(StringComparer.OrdinalIgnoreCase)
diff --git a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs
index 2cbb30bcb00..45011edb908 100644
--- a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs
+++ b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs
@@ -636,6 +636,8 @@ private PSModuleInfo ImportModule_LocallyViaName_WithTelemetry(ImportModuleOptio
private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModuleOptions, string name)
{
+ bool shallWriteError = !importModuleOptions.SkipSystem32ModulesAndSuppressError;
+
try
{
bool found = false;
@@ -671,14 +673,14 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul
}
bool alreadyLoaded = false;
- if (!string.IsNullOrEmpty(rootedPath))
+ var manifestProcessingFlags = ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.NullOnFirstError;
+ if (shallWriteError)
{
- // TODO/FIXME: use IsModuleAlreadyLoaded to get consistent behavior
- // TODO/FIXME: (for example checking ModuleType != Manifest below seems incorrect - cdxml modules also declare their own version)
- // PSModuleInfo alreadyLoadedModule = null;
- // TryGetFromModuleTable(rootedPath, out alreadyLoadedModule);
- // if (!BaseForce && IsModuleAlreadyLoaded(alreadyLoadedModule))
+ manifestProcessingFlags |= ManifestProcessingFlags.WriteErrors;
+ }
+ if (!string.IsNullOrEmpty(rootedPath))
+ {
// If the module has already been loaded, just emit it and continue...
if (!BaseForce && TryGetFromModuleTable(rootedPath, out PSModuleInfo module))
{
@@ -725,9 +727,14 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul
RemoveModule(moduleToRemove);
}
- foundModule = LoadModule(rootedPath, null, this.BasePrefix, null, ref importModuleOptions,
- ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError,
- out found);
+ foundModule = LoadModule(
+ fileName: rootedPath,
+ moduleBase: null,
+ prefix: BasePrefix,
+ ss: null, /*SessionState*/
+ ref importModuleOptions,
+ manifestProcessingFlags,
+ out found);
}
else if (Directory.Exists(rootedPath))
{
@@ -738,21 +745,24 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul
}
// Load the latest valid version if it is a multi-version module directory
- foundModule = LoadUsingMultiVersionModuleBase(rootedPath,
- ManifestProcessingFlags.LoadElements |
- ManifestProcessingFlags.WriteErrors |
- ManifestProcessingFlags.NullOnFirstError,
- importModuleOptions, out found);
+ foundModule = LoadUsingMultiVersionModuleBase(rootedPath, manifestProcessingFlags, importModuleOptions, out found);
if (!found)
{
// If the path is a directory, double up the end of the string
// then try to load that using extensions...
rootedPath = Path.Combine(rootedPath, Path.GetFileName(rootedPath));
- foundModule = LoadUsingExtensions(null, rootedPath, rootedPath, null, null, this.BasePrefix, /*SessionState*/ null,
- importModuleOptions,
- ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError,
- out found);
+ foundModule = LoadUsingExtensions(
+ parentModule: null,
+ moduleName: rootedPath,
+ fileBaseName: rootedPath,
+ extension: null,
+ moduleBase: null,
+ prefix: BasePrefix,
+ ss: null, /*SessionState*/
+ importModuleOptions,
+ manifestProcessingFlags,
+ out found);
}
}
}
@@ -762,7 +772,7 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul
// Check if module could be a snapin. This was the case for PowerShell version 2 engine modules.
if (InitialSessionState.IsEngineModule(name))
{
- PSSnapInInfo snapin = ModuleCmdletBase.GetEngineSnapIn(Context, name);
+ PSSnapInInfo snapin = Context.CurrentRunspace.InitialSessionState.GetPSSnapIn(name);
// Return the command if we found a module
if (snapin != null)
@@ -786,16 +796,28 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul
// If there is no extension, we'll have to search using the extensions
if (!string.IsNullOrEmpty(Path.GetExtension(name)))
{
- foundModule = LoadModule(name, null, this.BasePrefix, null, ref importModuleOptions,
- ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError,
- out found);
+ foundModule = LoadModule(
+ fileName: name,
+ moduleBase: null,
+ prefix: BasePrefix,
+ ss: null, /*SessionState*/
+ ref importModuleOptions,
+ manifestProcessingFlags,
+ out found);
}
else
{
- foundModule = LoadUsingExtensions(null, name, name, null, null, this.BasePrefix, /*SessionState*/ null,
- importModuleOptions,
- ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError,
- out found);
+ foundModule = LoadUsingExtensions(
+ parentModule: null,
+ moduleName: name,
+ fileBaseName: name,
+ extension: null,
+ moduleBase: null,
+ prefix: BasePrefix,
+ ss: null, /*SessionState*/
+ importModuleOptions,
+ manifestProcessingFlags,
+ out found);
}
}
else
@@ -807,14 +829,17 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul
this.AddToAppDomainLevelCache = true;
}
- found = LoadUsingModulePath(found, modulePath, name, /* SessionState*/ null,
- importModuleOptions,
- ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError,
- out foundModule);
+ found = LoadUsingModulePath(
+ modulePath,
+ name,
+ ss: null, /* SessionState*/
+ importModuleOptions,
+ manifestProcessingFlags,
+ out foundModule);
}
}
- if (!found)
+ if (!found && shallWriteError)
{
ErrorRecord er = null;
string message = null;
@@ -856,8 +881,10 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul
}
catch (PSInvalidOperationException e)
{
- ErrorRecord er = new ErrorRecord(e.ErrorRecord, e);
- WriteError(er);
+ if (shallWriteError)
+ {
+ WriteError(new ErrorRecord(e.ErrorRecord, e));
+ }
}
return null;
@@ -1945,27 +1972,26 @@ private bool IsModuleInDenyList(string[] moduleDenyList, string moduleName, Modu
return match;
}
- private List FilterModuleCollection(IEnumerable moduleCollection)
+ private IEnumerable FilterModuleCollection(IEnumerable moduleCollection)
{
- List filteredModuleCollection = null;
- if (moduleCollection != null)
+ if (moduleCollection is null)
{
- // the ModuleDeny list is cached in PowerShellConfig object
- string[] moduleDenyList = PowerShellConfig.Instance.GetWindowsPowerShellCompatibilityModuleDenyList();
- if (moduleDenyList?.Any() != true)
- {
- filteredModuleCollection = new List(moduleCollection);
- }
- else
+ return null;
+ }
+
+ // The ModuleDeny list is cached in PowerShellConfig object
+ string[] moduleDenyList = PowerShellConfig.Instance.GetWindowsPowerShellCompatibilityModuleDenyList();
+ if (moduleDenyList is null || moduleDenyList.Length == 0)
+ {
+ return moduleCollection;
+ }
+
+ var filteredModuleCollection = new List();
+ foreach (var module in moduleCollection)
+ {
+ if (!IsModuleInDenyList(moduleDenyList, module as string, module as ModuleSpecification))
{
- filteredModuleCollection = new List();
- foreach (var module in moduleCollection)
- {
- if (!IsModuleInDenyList(moduleDenyList, module as string, module as ModuleSpecification))
- {
- filteredModuleCollection.Add(module);
- }
- }
+ filteredModuleCollection.Add(module);
}
}
@@ -1977,38 +2003,73 @@ private void PrepareNoClobberWinCompatModuleImport(string moduleName, ModuleSpec
Debug.Assert(string.IsNullOrEmpty(moduleName) ^ (moduleSpec == null), "Either moduleName or moduleSpec must be specified");
// moduleName can be just a module name and it also can be a full path to psd1 from which we need to extract the module name
- string coreModuleToLoad = ModuleIntrinsics.GetModuleName(moduleSpec == null ? moduleName : moduleSpec.Name);
+ string moduleToLoad = ModuleIntrinsics.GetModuleName(moduleSpec is null ? moduleName : moduleSpec.Name);
+
+ var isBuiltInModule = BuiltInModules.TryGetValue(moduleToLoad, out string normalizedName);
+ if (isBuiltInModule)
+ {
+ moduleToLoad = normalizedName;
+ }
- var isModuleToLoadEngineModule = InitialSessionState.IsEngineModule(coreModuleToLoad);
string[] noClobberModuleList = PowerShellConfig.Instance.GetWindowsPowerShellCompatibilityNoClobberModuleList();
- if (isModuleToLoadEngineModule || ((noClobberModuleList != null) && noClobberModuleList.Contains(coreModuleToLoad, StringComparer.OrdinalIgnoreCase)))
+ if (isBuiltInModule || noClobberModuleList?.Contains(moduleToLoad, StringComparer.OrdinalIgnoreCase) == true)
{
- // if it is one of engine modules - first try to load it from $PSHOME\Modules
- // otherwise rely on $env:PSModulePath (in which WinPS module location has to go after CorePS module location)
- if (isModuleToLoadEngineModule)
+ bool shouldLoadModuleLocally = true;
+ if (isBuiltInModule)
{
- string expectedCoreModulePath = Path.Combine(ModuleIntrinsics.GetPSHomeModulePath(), coreModuleToLoad);
- if (Directory.Exists(expectedCoreModulePath))
+ PSSnapInInfo loadedSnapin = Context.CurrentRunspace.InitialSessionState.GetPSSnapIn(moduleToLoad);
+ shouldLoadModuleLocally = loadedSnapin is null;
+
+ if (shouldLoadModuleLocally)
{
- coreModuleToLoad = expectedCoreModulePath;
+ // If it is one of built-in modules, first try loading it from $PSHOME\Modules, otherwise rely on $env:PSModulePath.
+ string expectedCoreModulePath = Path.Combine(ModuleIntrinsics.GetPSHomeModulePath(), moduleToLoad);
+ if (Directory.Exists(expectedCoreModulePath))
+ {
+ moduleToLoad = expectedCoreModulePath;
+ }
}
}
- if (moduleSpec == null)
- {
- ImportModule_LocallyViaName_WithTelemetry(importModuleOptions, coreModuleToLoad);
- }
- else
+ if (shouldLoadModuleLocally)
{
- ModuleSpecification tmpModuleSpec = new ModuleSpecification()
+ // Here we want to load a core-edition compatible version of the module, so the loading procedure will skip
+ // the 'System32' module path when searching. Also, we want to suppress writing out errors in case that a
+ // core-compatible version of the module cannot be found, because:
+ // 1. that's OK as long as it's not a PowerShell built-in module such as the 'Utility' moudle;
+ // 2. the error message will be confusing to the user.
+ bool savedValue = importModuleOptions.SkipSystem32ModulesAndSuppressError;
+ importModuleOptions.SkipSystem32ModulesAndSuppressError = true;
+
+ PSModuleInfo moduleInfo = moduleSpec is null
+ ? ImportModule_LocallyViaName_WithTelemetry(importModuleOptions, moduleToLoad)
+ : ImportModule_LocallyViaFQName(
+ importModuleOptions,
+ new ModuleSpecification()
+ {
+ Guid = moduleSpec.Guid,
+ MaximumVersion = moduleSpec.MaximumVersion,
+ Version = moduleSpec.Version,
+ RequiredVersion = moduleSpec.RequiredVersion,
+ Name = moduleToLoad
+ });
+
+ // If we failed to load a core-compatible version of a built-in module, we should stop trying to load the
+ // module in 'WinCompat' mode and report an error. This could happen when a user didn't correctly deploy
+ // the built-in modules, which would result in very confusing errors when the module auto-loading silently
+ // attempts to load those built-in modules in 'WinCompat' mode from the 'System32' module path.
+ //
+ // If the loading failed but it's NOT a built-in module, then it's fine to ignore this failure and continue
+ // to load the module in 'WinCompat' mode.
+ if (moduleInfo is null && isBuiltInModule)
{
- Guid = moduleSpec.Guid,
- MaximumVersion = moduleSpec.MaximumVersion,
- Version = moduleSpec.Version,
- RequiredVersion = moduleSpec.RequiredVersion,
- Name = coreModuleToLoad
- };
- ImportModule_LocallyViaFQName(importModuleOptions, tmpModuleSpec);
+ throw new InvalidOperationException(
+ StringUtil.Format(
+ Modules.CannotFindCoreCompatibleBuiltInModule,
+ moduleToLoad));
+ }
+
+ importModuleOptions.SkipSystem32ModulesAndSuppressError = savedValue;
}
importModuleOptions.NoClobberExportPSSession = true;
@@ -2020,28 +2081,15 @@ internal override IList ImportModulesUsingWinCompat(IEnumerable moduleProxyList = new List();
#if !UNIX
// one of the two parameters can be passed: either ModuleNames (most of the time) or ModuleSpecifications (they are used in different parameter sets)
- List filteredModuleNames = FilterModuleCollection(moduleNames);
- List filteredModuleFullyQualifiedNames = FilterModuleCollection(moduleFullyQualifiedNames);
+ IEnumerable filteredModuleNames = FilterModuleCollection(moduleNames);
+ IEnumerable filteredModuleFullyQualifiedNames = FilterModuleCollection(moduleFullyQualifiedNames);
// do not setup WinCompat resources if we have no modules to import
- if ((filteredModuleNames?.Any() != true) && (filteredModuleFullyQualifiedNames?.Any() != true))
+ if (filteredModuleNames?.Any() != true && filteredModuleFullyQualifiedNames?.Any() != true)
{
return moduleProxyList;
}
- 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();
- }
-
// perform necessary preparations if module has to be imported with NoClobber mode
if (filteredModuleNames != null)
{
@@ -2059,13 +2107,26 @@ internal override IList ImportModulesUsingWinCompat(IEnumerable();
+ }
+
// perform the module import / proxy generation
moduleProxyList = ImportModule_RemotelyViaPsrpSession(importModuleOptions, filteredModuleNames, filteredModuleFullyQualifiedNames, WindowsPowerShellCompatRemotingSession, usingWinCompat: true);
foreach (PSModuleInfo moduleProxy in moduleProxyList)
{
moduleProxy.IsWindowsPowerShellCompatModule = true;
- System.Threading.Interlocked.Increment(ref s_WindowsPowerShellCompatUsageCounter);
+ Interlocked.Increment(ref s_WindowsPowerShellCompatUsageCounter);
string message = StringUtil.Format(Modules.WinCompatModuleWarning, moduleProxy.Name, WindowsPowerShellCompatRemotingSession.Name);
WriteWarning(message);
diff --git a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs
index 90019e3bfed..a4d1a7057f1 100644
--- a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs
+++ b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs
@@ -105,6 +105,12 @@ protected internal struct ImportModuleOptions
/// Historically -AllowClobber in these scenarios was set as True.
///
internal bool NoClobberExportPSSession;
+
+ ///
+ /// Flag that controls skipping the System32 module path when searching a module in module paths. It also suppresses
+ /// writing out errors when specified.
+ ///
+ internal bool SkipSystem32ModulesAndSuppressError;
}
///
@@ -280,6 +286,21 @@ internal List MatchAll
"ModuleVersion"
};
+ ///
+ /// List of PowerShell built-in modules that are shipped with PowerShell only, not on PS Gallery.
+ ///
+ protected static readonly HashSet BuiltInModules = new(StringComparer.OrdinalIgnoreCase)
+ {
+ "CimCmdlets",
+ "Microsoft.PowerShell.Diagnostics",
+ "Microsoft.PowerShell.Host",
+ "Microsoft.PowerShell.Management",
+ "Microsoft.PowerShell.Security",
+ "Microsoft.PowerShell.Utility",
+ "Microsoft.WSMan.Management",
+ "PSDiagnostics",
+ };
+
///
/// When module manifests lack a CompatiblePSEditions field,
/// they will be treated as if they have this value.
@@ -307,14 +328,25 @@ internal List MatchAll
private readonly Dictionary _currentlyProcessingModules = new Dictionary();
- internal bool LoadUsingModulePath(bool found, IEnumerable modulePath, string name, SessionState ss,
- ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, out PSModuleInfo module)
+ internal bool LoadUsingModulePath(
+ IEnumerable modulePath,
+ string name,
+ SessionState ss,
+ ImportModuleOptions options,
+ ManifestProcessingFlags manifestProcessingFlags,
+ out PSModuleInfo module)
{
- return LoadUsingModulePath(null, found, modulePath, name, ss, options, manifestProcessingFlags, out module);
+ return LoadUsingModulePath(parentModule: null, modulePath, name, ss, options, manifestProcessingFlags, out module);
}
- internal bool LoadUsingModulePath(PSModuleInfo parentModule, bool found, IEnumerable modulePath, string name, SessionState ss,
- ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, out PSModuleInfo module)
+ internal bool LoadUsingModulePath(
+ PSModuleInfo parentModule,
+ IEnumerable modulePath,
+ string name,
+ SessionState ss,
+ ImportModuleOptions options,
+ ManifestProcessingFlags manifestProcessingFlags,
+ out PSModuleInfo module)
{
string extension = Path.GetExtension(name);
string fileBaseName;
@@ -325,11 +357,18 @@ internal bool LoadUsingModulePath(PSModuleInfo parentModule, bool found, IEnumer
extension = null;
}
else
+ {
fileBaseName = name.Substring(0, name.Length - extension.Length);
+ }
// Now search using the module path...
+ bool found = false;
foreach (string path in modulePath)
{
+ if (options.SkipSystem32ModulesAndSuppressError && ModuleUtils.IsOnSystem32ModulePath(path))
+ {
+ continue;
+ }
#if UNIX
foreach (string folder in Directory.EnumerateDirectories(path))
{
@@ -823,7 +862,7 @@ private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, Module
}
// Otherwise try the module path
- found = LoadUsingModulePath(parentModule, found, modulePath,
+ found = LoadUsingModulePath(parentModule, modulePath,
moduleSpecification.Name, ss,
options, manifestProcessingFlags, out module);
}
@@ -2375,17 +2414,13 @@ internal PSModuleInfo LoadModuleManifest(
{
if (importingModule)
{
- IList moduleProxies = ImportModulesUsingWinCompat(new string[] { moduleManifestPath }, null, options);
+ IList moduleProxies = ImportModulesUsingWinCompat(
+ moduleNames: new string[] { moduleManifestPath },
+ moduleFullyQualifiedNames: null,
+ importModuleOptions: options);
- // we are loading by a single ManifestPath so expect max of 1
- if (moduleProxies.Count > 0)
- {
- return moduleProxies[0];
- }
- else
- {
- return null;
- }
+ // We are loading by a single ManifestPath so expect max of 1
+ return moduleProxies.Count > 0 ? moduleProxies[0] : null;
}
}
else
@@ -3706,7 +3741,7 @@ internal static object IsModuleLoaded(ExecutionContext context, ModuleSpecificat
// If the RequiredModule is one of the Engine modules, then they could have been loaded as snapins (using InitialSessionState.CreateDefault())
if (result == null && InitialSessionState.IsEngineModule(requiredModule.Name))
{
- result = ModuleCmdletBase.GetEngineSnapIn(context, requiredModule.Name);
+ result = context.CurrentRunspace.InitialSessionState.GetPSSnapIn(requiredModule.Name);
if (result != null)
{
loaded = true;
@@ -4883,7 +4918,7 @@ internal static void SyncCurrentLocationHandler(object sender, LocationChangedEv
}
}
- internal static System.EventHandler SyncCurrentLocationDelegate;
+ internal static EventHandler SyncCurrentLocationDelegate;
internal virtual IList ImportModulesUsingWinCompat(IEnumerable moduleNames, IEnumerable moduleFullyQualifiedNames, ImportModuleOptions importModuleOptions) { throw new System.NotImplementedException(); }
@@ -5309,9 +5344,8 @@ internal PSModuleInfo LoadUsingExtensions(PSModuleInfo parentModule,
string moduleName, string fileBaseName, string extension, string moduleBase,
string prefix, SessionState ss, ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, out bool found)
{
- bool throwAwayModuleFileFound = false;
return LoadUsingExtensions(parentModule, moduleName, fileBaseName, extension, moduleBase, prefix, ss,
- options, manifestProcessingFlags, out found, out throwAwayModuleFileFound);
+ options, manifestProcessingFlags, out found, out _);
}
///
@@ -5412,7 +5446,6 @@ internal PSModuleInfo LoadUsingExtensions(PSModuleInfo parentModule,
}
else if (File.Exists(fileName))
{
- moduleFileFound = true;
// Win8: 325243 - Added the version check so that we do not unload modules with the same name but different version
if (BaseForce && DoesAlreadyLoadedModuleSatisfyConstraints(module))
{
@@ -7359,29 +7392,6 @@ private static void ValidateCommandName(ModuleCmdletBase cmdlet,
}
}
- ///
- /// Search a PSSnapin with the specified name.
- ///
- internal static PSSnapInInfo GetEngineSnapIn(ExecutionContext context, string name)
- {
- HashSet snapinSet = new HashSet();
- List cmdlets = context.SessionState.InvokeCommand.GetCmdlets();
- foreach (CmdletInfo cmdlet in cmdlets)
- {
- PSSnapInInfo snapin = cmdlet.PSSnapIn;
- if (snapin != null && !snapinSet.Contains(snapin))
- snapinSet.Add(snapin);
- }
-
- foreach (PSSnapInInfo snapin in snapinSet)
- {
- if (string.Equals(snapin.Name, name, StringComparison.OrdinalIgnoreCase))
- return snapin;
- }
-
- return null;
- }
-
///
/// Returns the context cached ModuleTable module for import only if found and has safe language boundaries while
/// exporting all functions by default.
diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs
index 1d8fe6bc8b5..ada03fb4914 100644
--- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs
+++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs
@@ -976,20 +976,17 @@ internal static string GetPSHomeModulePath()
try
{
string psHome = Utils.DefaultPowerShellAppBase;
- if (!string.IsNullOrEmpty(psHome))
- {
- // Win8: 584267 Powershell Modules are listed twice in x86, and cannot be removed
- // This happens because ModuleTable uses Path as the key and CBS installer
- // expands the path to include "SysWOW64" (for
- // HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\PowerShell\3\PowerShellEngine ApplicationBase).
- // Because of this, the module that is getting loaded during startup (through LocalRunspace)
- // is using "SysWow64" in the key. Later, when Import-Module is called, it loads the
- // module using ""System32" in the key.
#if !UNIX
- psHome = psHome.ToLowerInvariant().Replace("\\syswow64\\", "\\system32\\");
+ // Win8: 584267 Powershell Modules are listed twice in x86, and cannot be removed.
+ // This happens because 'ModuleTable' uses Path as the key and x86 WinPS has "SysWOW64" in its $PSHOME.
+ // Because of this, the module that is getting loaded during startup (through LocalRunspace) is using
+ // "SysWow64" in the key. Later, when 'Import-Module' is called, it loads the module using ""System32"
+ // in the key.
+ // For the cross-platform PowerShell, a user can choose to install it under "C:\Windows\SysWOW64", and
+ // thus it may have the same problem as described above. So we keep this line of code.
+ psHome = psHome.ToLowerInvariant().Replace(@"\syswow64\", @"\system32\");
#endif
- Interlocked.CompareExchange(ref s_psHomeModulePath, Path.Combine(psHome, "Modules"), null);
- }
+ Interlocked.CompareExchange(ref s_psHomeModulePath, Path.Combine(psHome, "Modules"), null);
}
catch (System.Security.SecurityException)
{
diff --git a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs
index 26d3174ca3b..8dab2283fc5 100644
--- a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs
+++ b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs
@@ -121,7 +121,7 @@ internal static bool IsPSEditionCompatible(
#if UNIX
return true;
#else
- if (!ModuleUtils.IsOnSystem32ModulePath(moduleManifestPath))
+ if (!IsOnSystem32ModulePath(moduleManifestPath))
{
return true;
}
diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs
index 7ddd657872e..f2e9bd9093e 100644
--- a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs
+++ b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs
@@ -221,11 +221,7 @@ public override InitialSessionState InitialSessionState
{
get
{
-#pragma warning disable 56503
-
throw PSTraceSource.NewNotImplementedException();
-
-#pragma warning restore 56503
}
}
@@ -236,11 +232,7 @@ public override JobManager JobManager
{
get
{
-#pragma warning disable 56503
-
throw PSTraceSource.NewNotImplementedException();
-
-#pragma warning restore 56503
}
}
diff --git a/src/System.Management.Automation/resources/DiscoveryExceptions.resx b/src/System.Management.Automation/resources/DiscoveryExceptions.resx
index d3923969e8d..2d8445deb4f 100644
--- a/src/System.Management.Automation/resources/DiscoveryExceptions.resx
+++ b/src/System.Management.Automation/resources/DiscoveryExceptions.resx
@@ -206,6 +206,10 @@ The #requires statement must be in one of the following formats:
The '{0}' command was found in the module '{1}', but the module could not be loaded. For more information, run 'Import-Module {1}'.
+
+ The '{0}' command was found in the module '{1}', but the module could not be loaded due to the following error: [{2}]
+For more information, run 'Import-Module {1}'.
+
The module '{0}' could not be loaded. For more information, run 'Import-Module {0}'.
diff --git a/src/System.Management.Automation/resources/Modules.resx b/src/System.Management.Automation/resources/Modules.resx
index 345811502b6..2e50a3c5317 100644
--- a/src/System.Management.Automation/resources/Modules.resx
+++ b/src/System.Management.Automation/resources/Modules.resx
@@ -624,4 +624,7 @@
Cannot create new module while the session is in ConstrainedLanguage mode.
+
+ Cannot find the built-in module '{0}' that is compatible with the 'Core' edition. Please make sure the PowerShell built-in modules are available. They usually come with the PowerShell package under the $PSHOME module path, and are required for PowerShell to function properly.
+
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 72a7c9eddb1..f9094254ec3 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Core/CompatiblePSEditions.Module.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Core/CompatiblePSEditions.Module.Tests.ps1
@@ -11,7 +11,7 @@ function Add-ModulePath
if ($Prepend)
{
- $env:PSModulePAth = $Path + [System.IO.Path]::PathSeparator + $env:PSModulePath
+ $env:PSModulePath = $Path + [System.IO.Path]::PathSeparator + $env:PSModulePath
}
else
{
@@ -131,6 +131,22 @@ function New-TestNestedModule
[scriptblock]::Create($newManifestCmd).Invoke()
}
+function Get-DesktopModuleToUse {
+ $system32Path = "$env:windir\system32\WindowsPowerShell\v1.0\Modules"
+ $persistentMemoryModule = "PersistentMemory"
+ $remoteDesktopModule = "RemoteDesktop"
+
+ if (Test-Path -PathType Container "$system32Path\$persistentMemoryModule") {
+ return $persistentMemoryModule
+ } elseif (Test-Path -PathType Container "$system32Path\$remoteDesktopModule") {
+ return $remoteDesktopModule
+ } else {
+ return $null
+ }
+}
+
+$desktopModuleToUse = Get-DesktopModuleToUse
+
Describe "Get-Module with CompatiblePSEditions-checked paths" -Tag "CI" {
BeforeAll {
@@ -643,6 +659,37 @@ Describe "Additional tests for Import-Module with WinCompat" -Tag "Feature" {
'{"Microsoft.PowerShell:ExecutionPolicy": "RemoteSigned", "WindowsPowerShellCompatibilityNoClobberModuleList": ["' + $ModuleName2 + '"]}' | Out-File -Force $ConfigPath
& $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `'$basePath`');Test-${ModuleName2}PSEdition;Test-$ModuleName2" | Should -Be @('Desktop','Core')
}
+
+ It "NoClobber WinCompat list in powershell.config is a Desktop-edition module" {
+ if (-not $desktopModuleToUse) {
+ throw 'Neither the "PersistentMemory" module nor the "RemoteDesktop" module is available. Please check and use a desktop-edition module that is under the System32 module path.'
+ }
+
+ ## The 'Desktop' edition module 'PersistentMemory' (available on Windows Client) or 'RemoteDesktop' (available on Windows Server) should not be imported twice.
+ $ConfigPath = Join-Path $TestDrive 'powershell.config.json'
+@"
+{"Microsoft.PowerShell:ExecutionPolicy": "RemoteSigned", "WindowsPowerShellCompatibilityNoClobberModuleList": ["$desktopModuleToUse"]}
+"@ | Out-File -Force $ConfigPath
+ $env:PSModulePath = ''
+
+ ## The desktop-edition module is listed in the no-clobber list, so we will first try loading a core-edition
+ ## compatible version of the module before loading the remote one. The 'system32' module path will be skipped
+ ## in this attempt, which is by-design.
+ ## If we don't skip the 'system32' module path in this loading attempt, the desktop-edition module will be
+ ## imported twice as a remote module, and then 'Remove-Module' won't close the WinCompat session.
+ $script = @"
+Import-Module $desktopModuleToUse -UseWindowsPowerShell -WarningAction Ignore
+Get-Module $desktopModuleToUse | ForEach-Object { `$_.ModuleType.ToString() }
+(Get-PSSession | Measure-Object).Count
+Remove-Module $desktopModuleToUse
+(Get-PSSession | Measure-Object).Count
+"@
+ $scriptBlock = [scriptblock]::Create($script)
+ $results = & $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c $scriptBlock
+ $results[0] | Should -BeExactly 'Script'
+ $results[1] | Should -BeExactly 1
+ $results[2] | Should -BeExactly 0
+ }
}
Context "Tests around PSModulePath in WinCompat process" {
@@ -1343,3 +1390,123 @@ Describe "Import-Module nested module behaviour with Edition checking" -Tag "Fea
}
}
}
+
+Describe "WinCompat importing should check availablity of built-in modules" -Tag "CI" {
+ BeforeAll {
+ if (-not $IsWindows ) {
+ $originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
+ $PSDefaultParameterValues["it:skip"] = $true
+ return
+ }
+
+ ## Copy the current PowerShell instance to a temp location
+ $tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "WinCompat"
+ $pwshDir = Join-Path $tempDir "pwsh"
+ $moduleDir = Join-Path $tempDir "Modules"
+ $savedModulePath = $env:PSModulePath
+
+ if (Test-Path $tempDir) {
+ Remove-Item $tempDir -Recurse -Force
+ }
+
+ Write-Host "Making a copy of the running PowerShell instance ..." -ForegroundColor Yellow
+ Copy-Item $PSHOME $pwshDir -Recurse -Force
+ Move-Item $pwshDir\Modules $moduleDir -Force
+ Write-Host "-- Done copying!" -ForegroundColor Yellow
+ }
+
+ AfterAll {
+ if (-not $IsWindows) {
+ $global:PSDefaultParameterValues = $originalDefaultParameterValues
+ return
+ }
+
+ $env:PSModulePath = $savedModulePath
+ Remove-Item $tempDir -Recurse -Force
+ }
+
+ It "Missing built-in modules will trigger error instead of loading the non-compatible ones in System32 directory. Running ''" -TestCases @(
+ @{
+ Command = 'Start-Transcript';
+ FullyQualifiedErrorId = "CouldNotAutoloadMatchingModule";
+ ExceptionMessage = "*'Start-Transcript'*'Microsoft.PowerShell.Host'*'Microsoft.PowerShell.Host'*'Core'*`$PSHOME*'Import-Module Microsoft.PowerShell.Host'*"
+ }
+ @{
+ Command = 'Import-Module Microsoft.PowerShell.Host';
+ FullyQualifiedErrorId = "System.InvalidOperationException,Microsoft.PowerShell.Commands.ImportModuleCommand"
+ ExceptionMessage = "*'Microsoft.PowerShell.Host'*'Core'*`$PSHOME*"
+ }
+ @{
+ Command = 'Import-Module CimCmdlets'
+ FullyQualifiedErrorId = "System.InvalidOperationException,Microsoft.PowerShell.Commands.ImportModuleCommand"
+ ExceptionMessage = "*'CimCmdlets'*'Core'*`$PSHOME*"
+ }
+ @{
+ Command = 'Import-Module Microsoft.PowerShell.Utility'
+ FullyQualifiedErrorId = "System.InvalidOperationException,Microsoft.PowerShell.Commands.ImportModuleCommand"
+ ExceptionMessage = "*'Microsoft.PowerShell.Utility'*'Core'*`$PSHOME*"
+ }
+ ) {
+ param(
+ $Command,
+ $FullyQualifiedErrorId,
+ $ExceptionMessage
+ )
+
+ $template = @'
+ try {{
+ {0}
+ }} catch {{
+ $_.FullyQualifiedErrorId
+ $_.Exception.Message
+ }}
+'@
+ $env:PSModulePath = ''
+ $script = $template -f $Command
+ $scriptBlock = [scriptblock]::Create($script)
+
+ $result = & "$pwshDir\pwsh.exe" -NoProfile -NonInteractive -c $scriptBlock
+ $result | Should -HaveCount 2
+ $result[0] | Should -BeExactly $FullyQualifiedErrorId
+ $result[1] | Should -BeLike $ExceptionMessage
+ }
+
+ It "Attempt to load a 'Desktop' edition module should fail because 'Export-PSSession' cannot be found" {
+ if (-not $desktopModuleToUse) {
+ throw 'Neither the "PersistentMemory" module nor the "RemoteDesktop" module is available. Please check and use a desktop-edition module that is under the System32 module path.'
+ }
+
+ $script = @"
+ try {
+ Import-Module $desktopModuleToUse -ErrorAction Stop
+ } catch {
+ `$_.FullyQualifiedErrorId
+ `$_.Exception.Message
+ }
+"@
+ $env:PSModulePath = ''
+ $scriptBlock = [scriptblock]::Create($script)
+ $result = & "$pwshDir\pwsh.exe" -NoProfile -NonInteractive -c $scriptBlock
+ $result | Should -HaveCount 2
+ $result[0] | Should -BeExactly "CommandNotFoundException,Microsoft.PowerShell.Commands.ImportModuleCommand"
+ $result[1] | Should -BeLike "*'$desktopModuleToUse'*'Export-PSSession'*'Microsoft.PowerShell.Utility'*"
+ }
+
+ It "When built-in modules are available but not in `$PSHOME module path, things should work" {
+ $env:PSModulePath = ''
+ $result = & "$pwshDir\pwsh.exe" -NoProfile -NonInteractive -c @"
+ `$env:PSModulePath += ';$moduleDir'
+ Import-Module Microsoft.PowerShell.Utility -UseWindowsPowerShell -WarningAction Ignore
+ Get-Module Microsoft.PowerShell.Utility | ForEach-Object ModuleType
+ Get-Module Microsoft.PowerShell.Utility | Where-Object ModuleType -eq 'Manifest' | ForEach-Object Path
+ Get-Module Microsoft.PowerShell.Utility | Where-Object ModuleType -eq 'Script' | ForEach-Object { `$_.ExportedCommands.Keys }
+"@
+ $result | Should -HaveCount 6
+ $result[0] | Should -BeExactly 'Manifest'
+ $result[1] | Should -BeExactly 'Script'
+ $result[2] | Should -BeExactly "$moduleDir\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psd1"
+ $result[3] | Should -BeExactly 'Convert-String'
+ $result[4] | Should -BeExactly 'ConvertFrom-String'
+ $result[5] | Should -BeExactly 'CFS'
+ }
+}
From ed6e0588c8395d779159df537b3d6c5320e0689c Mon Sep 17 00:00:00 2001
From: Thomas-Yu <42897266+Thomas-Yu@users.noreply.github.com>
Date: Wed, 12 Jan 2022 11:17:02 -0800
Subject: [PATCH 009/209] Add `-StrictMode` to `Invoke-Command` to allow
specifying strict mode when invoking command locally (#16545)
---
.../ExperimentalFeature.cs | 4 +
.../remoting/commands/InvokeCommandCommand.cs | 122 ++++++++++++++++--
.../Invoke-Command.Tests.ps1 | 41 ++++++
3 files changed, 154 insertions(+), 13 deletions(-)
create mode 100644 test/powershell/Modules/Microsoft.PowerShell.Core/Invoke-Command.Tests.ps1
diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
index d23f5b63290..f6e3632e16e 100644
--- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
+++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
@@ -27,6 +27,7 @@ public class ExperimentalFeature
internal const string PSCleanBlockFeatureName = "PSCleanBlock";
internal const string PSAMSIMethodInvocationLogging = "PSAMSIMethodInvocationLogging";
internal const string PSExecFeatureName = "PSExec";
+ internal const string PSStrictModeAssignment = "PSStrictModeAssignment";
#endregion
@@ -142,6 +143,9 @@ static ExperimentalFeature()
new ExperimentalFeature(
name: PSExecFeatureName,
description: "Add 'exec' built-in command on Linux and macOS"),
+ new ExperimentalFeature(
+ name: PSStrictModeAssignment,
+ description: "Add support of setting Strict-Mode with Invoke-Command"),
};
EngineExperimentalFeatures = new ReadOnlyCollection(engineFeatures);
diff --git a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs
index c86f7903a1a..a3aca6de70c 100644
--- a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs
+++ b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs
@@ -263,6 +263,72 @@ public override SwitchParameter UseSSL
}
}
+ private sealed class ArgumentToPSVersionTransformationAttribute : ArgumentToVersionTransformationAttribute
+ {
+ protected override bool TryConvertFromString(string versionString, [NotNullWhen(true)] out Version version)
+ {
+ if (string.Equals("off", versionString, StringComparison.OrdinalIgnoreCase))
+ {
+ version = new Version(0, 0);
+ return true;
+ }
+
+ if (string.Equals("latest", versionString, StringComparison.OrdinalIgnoreCase))
+ {
+ version = PSVersionInfo.PSVersion;
+ return true;
+ }
+
+ return base.TryConvertFromString(versionString, out version);
+ }
+ }
+
+ private static readonly Version s_OffVersion = new Version(0, 0);
+
+ private sealed class ValidateVersionAttribute : ValidateArgumentsAttribute
+ {
+ protected override void Validate(object arguments, EngineIntrinsics engineIntrinsics)
+ {
+ Version version = arguments as Version;
+ if (version == s_OffVersion)
+ {
+ return;
+ }
+
+ if (version == null || !PSVersionInfo.IsValidPSVersion(version))
+ {
+ // No conversion succeeded so throw an exception...
+ throw new ValidationMetadataException(
+ "InvalidPSVersion",
+ null,
+ Metadata.ValidateVersionFailure,
+ arguments);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets strict mode.
+ ///
+ [Experimental(ExperimentalFeature.PSStrictModeAssignment, ExperimentAction.Show)]
+ [Parameter(ParameterSetName = InvokeCommandCommand.InProcParameterSet)]
+ [ArgumentToPSVersionTransformation]
+ [ValidateVersion]
+ public Version StrictMode
+ {
+ get
+ {
+ return _strictmodeversion;
+ }
+
+ set
+ {
+ _strictmodeversion = value;
+ }
+ }
+
+ private Version _strictmodeversion = null;
+
///
/// For WSMan session:
/// If this parameter is not specified then the value specified in
@@ -823,6 +889,8 @@ public virtual SwitchParameter RemoteDebug
#endregion
+ private Version _savedStrictModeVersion;
+
#endregion Parameters
#region Overrides
@@ -946,6 +1014,12 @@ protected override void BeginProcessing()
}
}
+ if (_strictmodeversion != null)
+ {
+ _savedStrictModeVersion = Context.EngineSessionState.CurrentScope.StrictModeVersion;
+ Context.EngineSessionState.CurrentScope.StrictModeVersion = _strictmodeversion;
+ }
+
return;
}
@@ -1162,7 +1236,19 @@ protected override void ProcessRecord()
}
else if (ParameterSetName.Equals(InvokeCommandCommand.InProcParameterSet) && (_steppablePipeline != null))
{
- _steppablePipeline.Process(InputObject);
+ try
+ {
+ _steppablePipeline.Process(InputObject);
+ }
+ catch
+ {
+ if (_strictmodeversion != null)
+ {
+ Context.EngineSessionState.CurrentScope.StrictModeVersion = _savedStrictModeVersion;
+ }
+
+ throw;
+ }
}
else
{
@@ -1193,20 +1279,30 @@ protected override void EndProcessing()
{
if (ParameterSetName.Equals(InvokeCommandCommand.InProcParameterSet))
{
- if (_steppablePipeline != null)
- {
- _steppablePipeline.End();
+ try
+ {
+ if (_steppablePipeline != null)
+ {
+ _steppablePipeline.End();
+ }
+ else
+ {
+ ScriptBlock.InvokeUsingCmdlet(
+ contextCmdlet: this,
+ useLocalScope: !NoNewScope,
+ errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe,
+ dollarUnder: AutomationNull.Value,
+ input: _input,
+ scriptThis: AutomationNull.Value,
+ args: ArgumentList);
+ }
}
- else
+ finally
{
- ScriptBlock.InvokeUsingCmdlet(
- contextCmdlet: this,
- useLocalScope: !NoNewScope,
- errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe,
- dollarUnder: AutomationNull.Value,
- input: _input,
- scriptThis: AutomationNull.Value,
- args: ArgumentList);
+ if (_strictmodeversion != null)
+ {
+ Context.EngineSessionState.CurrentScope.StrictModeVersion = _savedStrictModeVersion;
+ }
}
}
else
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Invoke-Command.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Invoke-Command.Tests.ps1
new file mode 100644
index 00000000000..1765e5d8217
--- /dev/null
+++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Invoke-Command.Tests.ps1
@@ -0,0 +1,41 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+Describe "Invoke-Command" -Tags "CI" {
+ Context "StrictMode tests" {
+ BeforeAll {
+ $skipTest = !($EnabledExperimentalFeatures -contains "PSStrictModeAssignment");
+ If (Test-Path Variable:InvokeCommand__Test) {
+ Remove-Item Variable:InvokeCommand__Test
+ }
+ }
+
+ It "Setting -StrictMode parameter with uninitialized variable throws error" -skip:$skipTest {
+ { Invoke-Command -StrictMode 3.0 {$InvokeCommand__Test} } | Should -Throw -ErrorId 'VariableIsUndefined'
+ }
+
+ It "Setting -StrictMode parameter with initialized variable does not throw error" -skip:$skipTest {
+ $InvokeCommand__Test = 'Something'
+ Invoke-Command -StrictMode 3.0 {$InvokeCommand__Test} | Should -Be 'Something'
+ Remove-Item Variable:InvokeCommand__Test
+ }
+
+ It "-StrictMode parameter sets StrictMode back to original state after process completes" -skip:$skipTest {
+ { Invoke-Command -StrictMode 3.0 {$InvokeCommand__Test} } | Should -Throw -ErrorId 'VariableIsUndefined'
+ { Invoke-Command {$InvokeCommand__Test} } | Should -Not -Throw
+ }
+
+ It "-StrictMode parameter works on piped input" -skip:$skipTest {
+ "There" | Invoke-Command -ScriptBlock { "Hello $input" } -StrictMode 3.0 | Should -Be 'Hello There'
+ { "There" | Invoke-Command -ScriptBlock { "Hello $InvokeCommand__Test" } -StrictMode 3.0 } | Should -Throw -ErrorId 'VariableIsUndefined'
+ }
+
+ It "-StrictMode latest works" -skip:$skipTest {
+ { Invoke-Command -StrictMode latest {$InvokeCommand__Test} } | Should -Throw -ErrorId 'VariableIsUndefined'
+ }
+
+ It "-StrictMode off works" -skip:$skipTest {
+ { Invoke-Command -StrictMode off {$InvokeCommand__Test} } | Should -Not -Throw
+ }
+ }
+}
From 87b61a04522b45c01513b7424a14d1362235c46c Mon Sep 17 00:00:00 2001
From: Steve Lee
Date: Wed, 12 Jan 2022 15:08:47 -0800
Subject: [PATCH 010/209] remove assert that is incorrect and affecting our
tests (#16588)
---
.../host/msh/PendingProgress.cs | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs
index fad667dba2d..df120a2d0e5 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs
@@ -937,8 +937,6 @@ internal override
return nodesCompressed;
}
- Dbg.Assert(false, "with all nodes invisible, we should never reach this point.");
-
return 0;
}
From 83a809b1f0ef4f24ce44aa5011a5495e2239b345 Mon Sep 17 00:00:00 2001
From: KiwiThePoodle <42877043+KiwiThePoodle@users.noreply.github.com>
Date: Thu, 13 Jan 2022 09:33:43 -0800
Subject: [PATCH 011/209] Make `Measure-Object` ignore missing properties
unless running in strict mode (#16589)
---
.../commands/utility/Measure-Object.cs | 13 +++++++-----
.../Measure-Object.Tests.ps1 | 20 +++++++++++++++++++
2 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs
index e6a99c38cb0..d885d0a64f1 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs
@@ -792,9 +792,9 @@ private void WritePropertyNotFoundError(string propertyName, string errorId)
{
Diagnostics.Assert(Property != null, "no property and no InputObject should have been addressed");
ErrorRecord errorRecord = new(
- PSTraceSource.NewArgumentException("Property"),
+ PSTraceSource.NewArgumentException(propertyName),
errorId,
- ErrorCategory.InvalidArgument,
+ ErrorCategory.ObjectNotFound,
null);
errorRecord.ErrorDetails = new ErrorDetails(
this, "MeasureObjectStrings", "PropertyNotFound", propertyName);
@@ -820,9 +820,12 @@ protected override void EndProcessing()
Statistics stat = _statistics[propertyName];
if (stat.count == 0 && Property != null)
{
- // Why are there two different ids for this error?
- string errorId = (IsMeasuringGeneric) ? "GenericMeasurePropertyNotFound" : "TextMeasurePropertyNotFound";
- WritePropertyNotFoundError(propertyName, errorId);
+ if (Context.IsStrictVersion(2))
+ {
+ string errorId = (IsMeasuringGeneric) ? "GenericMeasurePropertyNotFound" : "TextMeasurePropertyNotFound";
+ WritePropertyNotFoundError(propertyName, errorId);
+ }
+
continue;
}
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Measure-Object.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Measure-Object.Tests.ps1
index b1cf1b36d39..509afc36524 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Measure-Object.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Measure-Object.Tests.ps1
@@ -189,6 +189,26 @@ Describe "Measure-Object" -Tags "CI" {
}
}
+ Context "Empty folder tests" {
+ BeforeAll {
+ $repoPath = Join-Path -Path $TESTDRIVE -ChildPath "my_folder"
+ $testEmptyFolder = New-Item $repoPath -ItemType "directory"
+ $propertyName = "Length"
+ }
+
+ AfterAll {
+ Set-StrictMode -off
+ }
+
+ It "Should not throw an invalid property error when not in StrictMode" {
+ { Set-StrictMode -off; Get-Item $testEmptyFolder | Measure-Object $propertyName -ErrorAction Stop -sum } | Should -Not -Throw
+ }
+
+ It "Should throw an invalid property error when in StrictMode" {
+ { Set-StrictMode -version 3.0; Get-Item $testEmptyFolder | Measure-Object $propertyName -ErrorAction Stop -sum } | Should -Throw -ErrorId 'GenericMeasurePropertyNotFound,Microsoft.PowerShell.Commands.MeasureObjectCommand'
+ }
+ }
+
Context "String tests" {
BeforeAll {
$nl = [Environment]::NewLine
From da7c52a52ff8320a62321219a5b08cc2a0c52abe Mon Sep 17 00:00:00 2001
From: Sergey Zalyadeev
Date: Thu, 13 Jan 2022 23:28:03 +0300
Subject: [PATCH 012/209] Add lock and null check to remoting internals
(#16542) (#16683)
* fix crash Copy-Item to remote session (#16542)
* update comments
* remove lock (#16542)
Co-authored-by: Sergey Zalyadeev
---
.../remoting/fanin/PriorityCollection.cs | 24 ++++++++++++-------
1 file changed, 16 insertions(+), 8 deletions(-)
diff --git a/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs b/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs
index 7baebdc98ae..a29a202b5ae 100644
--- a/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs
+++ b/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs
@@ -220,21 +220,29 @@ internal byte[] ReadOrRegisterCallback(OnDataAvailableCallback callback,
lock (_readSyncObject)
{
priorityType = DataPriorityType.Default;
-
- // send data from which ever stream that has data directly.
+ // Send data from which ever stream that has data directly.
byte[] result = null;
- result = _dataToBeSent[(int)DataPriorityType.PromptResponse].ReadOrRegisterCallback(_onSendCollectionDataAvailable);
- priorityType = DataPriorityType.PromptResponse;
+ SerializedDataStream promptDataToBeSent = _dataToBeSent[(int)DataPriorityType.PromptResponse];
+ if (promptDataToBeSent is not null)
+ {
+ result = promptDataToBeSent.ReadOrRegisterCallback(_onSendCollectionDataAvailable);
+ priorityType = DataPriorityType.PromptResponse;
+ }
if (result == null)
{
- result = _dataToBeSent[(int)DataPriorityType.Default].ReadOrRegisterCallback(_onSendCollectionDataAvailable);
- priorityType = DataPriorityType.Default;
+ SerializedDataStream defaultDataToBeSent = _dataToBeSent[(int)DataPriorityType.Default];
+ if (defaultDataToBeSent is not null)
+ {
+ result = defaultDataToBeSent.ReadOrRegisterCallback(_onSendCollectionDataAvailable);
+ priorityType = DataPriorityType.Default;
+ }
}
- // no data to return..so register the callback.
+
+ // No data to return..so register the callback.
if (result == null)
{
- // register callback.
+ // Register callback.
_onDataAvailableCallback = callback;
}
From 686c84ccccaa9d9409de23d71a3108466faccacc Mon Sep 17 00:00:00 2001
From: "Mathias R. Jessen"
Date: Fri, 14 Jan 2022 02:39:54 +0100
Subject: [PATCH 013/209] Add new parameter to `Start-Sleep`: `[-Duration]
` (#16185)
---
.../commands/utility/StartSleepCommand.cs | 29 +++++
.../resources/StartSleepStrings.resx | 123 ++++++++++++++++++
.../engine/Attributes.cs | 59 +++++++++
.../Start-Sleep.Tests.ps1 | 24 ++++
4 files changed, 235 insertions(+)
create mode 100644 src/Microsoft.PowerShell.Commands.Utility/resources/StartSleepStrings.resx
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs
index a57d635f3eb..a1524e5ed4f 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs
@@ -55,6 +55,15 @@ public void Dispose()
[Alias("ms")]
public int Milliseconds { get; set; }
+ ///
+ /// Allows sleep time to be specified as a TimeSpan.
+ ///
+ [Parameter(Position = 0, Mandatory = true, ParameterSetName = "FromTimeSpan", ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true)]
+ [ValidateRange(ValidateRangeKind.NonNegative)]
+ [Alias("ts")]
+ public TimeSpan Duration { get; set; }
+
#endregion
#region methods
@@ -104,6 +113,26 @@ protected override void ProcessRecord()
case "Milliseconds":
sleepTime = Milliseconds;
break;
+
+ case "FromTimeSpan":
+ if (Duration.TotalMilliseconds > int.MaxValue)
+ {
+ PSArgumentException argumentException = PSTraceSource.NewArgumentException(
+ nameof(Duration),
+ StartSleepStrings.MaximumDurationExceeded,
+ TimeSpan.FromMilliseconds(int.MaxValue),
+ Duration);
+
+ ThrowTerminatingError(
+ new ErrorRecord(
+ argumentException,
+ "MaximumDurationExceeded",
+ ErrorCategory.InvalidArgument,
+ targetObject: null));
+ }
+
+ sleepTime = (int)Math.Floor(Duration.TotalMilliseconds);
+ break;
default:
Dbg.Diagnostics.Assert(false, "Only one of the specified parameter sets should be called.");
diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/StartSleepStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/StartSleepStrings.resx
new file mode 100644
index 00000000000..32804b9e21b
--- /dev/null
+++ b/src/Microsoft.PowerShell.Commands.Utility/resources/StartSleepStrings.resx
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ The '-Duration' parameter value must not exceed '{0}', provided value was '{1}'.
+
+
diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs
index a0411c78df7..9965824a927 100644
--- a/src/System.Management.Automation/engine/Attributes.cs
+++ b/src/System.Management.Automation/engine/Attributes.cs
@@ -1088,6 +1088,12 @@ public ValidateRangeAttribute(ValidateRangeKind kind) : base()
private static void ValidateRange(object element, ValidateRangeKind rangeKind)
{
+ if (element is TimeSpan ts)
+ {
+ ValidateTimeSpanRange(ts, rangeKind);
+ return;
+ }
+
Type commonType = GetCommonType(typeof(int), element.GetType());
if (commonType == null)
{
@@ -1212,6 +1218,59 @@ private void ValidateRange(object element)
}
}
+ private static void ValidateTimeSpanRange(TimeSpan element, ValidateRangeKind rangeKind)
+ {
+ TimeSpan zero = TimeSpan.Zero;
+
+ switch (rangeKind)
+ {
+ case ValidateRangeKind.Positive:
+ if (zero.CompareTo(element) >= 0)
+ {
+ throw new ValidationMetadataException(
+ "ValidateRangePositiveFailure",
+ null,
+ Metadata.ValidateRangePositiveFailure,
+ element.ToString());
+ }
+
+ break;
+ case ValidateRangeKind.NonNegative:
+ if (zero.CompareTo(element) > 0)
+ {
+ throw new ValidationMetadataException(
+ "ValidateRangeNonNegativeFailure",
+ null,
+ Metadata.ValidateRangeNonNegativeFailure,
+ element.ToString());
+ }
+
+ break;
+ case ValidateRangeKind.Negative:
+ if (zero.CompareTo(element) <= 0)
+ {
+ throw new ValidationMetadataException(
+ "ValidateRangeNegativeFailure",
+ null,
+ Metadata.ValidateRangeNegativeFailure,
+ element.ToString());
+ }
+
+ break;
+ case ValidateRangeKind.NonPositive:
+ if (zero.CompareTo(element) < 0)
+ {
+ throw new ValidationMetadataException(
+ "ValidateRangeNonPositiveFailure",
+ null,
+ Metadata.ValidateRangeNonPositiveFailure,
+ element.ToString());
+ }
+
+ break;
+ }
+ }
+
private static Type GetCommonType(Type minType, Type maxType)
{
Type resultType = null;
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Start-Sleep.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Start-Sleep.Tests.ps1
index 59ac3e9903d..e4899ce5439 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Start-Sleep.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Start-Sleep.Tests.ps1
@@ -23,6 +23,15 @@ Describe "Start-Sleep DRT Unit Tests" -Tags "CI" {
$watch.ElapsedMilliseconds | Should -BeLessThan $maxTime
}
+ It "Should work properly when sleeping with a [TimeSpan]" {
+ $duration = [timespan]::FromMilliseconds(1500)
+ $watch = [System.Diagnostics.Stopwatch]::StartNew()
+ Start-Sleep -Duration $duration
+ $watch.Stop()
+ $watch.ElapsedMilliseconds | Should -BeGreaterThan $minTime
+ $watch.ElapsedMilliseconds | Should -BeLessThan $maxTime
+ }
+
It "Should work properly when sleeping with ms alias" {
$watch = [System.Diagnostics.Stopwatch]::StartNew()
Start-Sleep -ms 1500
@@ -38,6 +47,21 @@ Describe "Start-Sleep DRT Unit Tests" -Tags "CI" {
$watch.ElapsedMilliseconds | Should -BeGreaterThan $minTime
$watch.ElapsedMilliseconds | Should -BeLessThan $maxTime
}
+
+ It "Should work properly when sleeping without parameters from [timespan]" {
+ $duration = [timespan]::FromMilliseconds(1500)
+ $watch = [System.Diagnostics.Stopwatch]::StartNew()
+ Start-Sleep $duration
+ $watch.Stop()
+ $watch.ElapsedMilliseconds | Should -BeGreaterThan $minTime
+ $watch.ElapsedMilliseconds | Should -BeLessThan $maxTime
+ }
+
+ It "Should validate [timespan] parameter values" {
+ { Start-Sleep -Duration '0:00:01' } | Should -Not -Throw
+ { Start-Sleep -Duration '-0:00:01' } | Should -Throw -ErrorId 'ParameterArgumentValidationError,Microsoft.PowerShell.Commands.StartSleepCommand'
+ { Start-Sleep -Duration '30.0:00:00' } | Should -Throw -ErrorId 'MaximumDurationExceeded,Microsoft.PowerShell.Commands.StartSleepCommand'
+ }
}
Describe "Start-Sleep" -Tags "CI" {
From 186d6be47d858460a3d0ba6abd81f779cb633b55 Mon Sep 17 00:00:00 2001
From: Dongbo Wang
Date: Thu, 13 Jan 2022 20:07:06 -0800
Subject: [PATCH 014/209] Use NotifyEndApplication to re-enable VT mode
(#16612)
Use NotifyEndApplication to re-enable VT mode instead of doing it in InputLoop.Run, so that we run this code only after invoking native commands
---
.../host/msh/ConsoleHost.cs | 39 +++++++++----------
.../host/msh/ConsoleHostUserInterface.cs | 22 +++++++----
.../engine/NativeCommandProcessor.cs | 15 ++++---
3 files changed, 40 insertions(+), 36 deletions(-)
diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
index 56492bc7e45..4703d54d880 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
@@ -1071,18 +1071,16 @@ public override void NotifyBeginApplication()
{
lock (hostGlobalLock)
{
- ++_beginApplicationNotifyCount;
- if (_beginApplicationNotifyCount == 1)
+ if (++_beginApplicationNotifyCount == 1)
{
- // save the window title when first notified.
-
+ // Save the window title when first notified.
_savedWindowTitle = ui.RawUI.WindowTitle;
#if !UNIX
if (_initialConsoleMode != ConsoleControl.ConsoleModes.Unknown)
{
- var activeScreenBufferHandle = ConsoleControl.GetActiveScreenBufferHandle();
- _savedConsoleMode = ConsoleControl.GetMode(activeScreenBufferHandle);
- ConsoleControl.SetMode(activeScreenBufferHandle, _initialConsoleMode);
+ var outputHandle = ConsoleControl.GetActiveScreenBufferHandle();
+ _savedConsoleMode = ConsoleControl.GetMode(outputHandle);
+ ConsoleControl.SetMode(outputHandle, _initialConsoleMode);
}
#endif
}
@@ -1097,17 +1095,26 @@ public override void NotifyEndApplication()
{
lock (hostGlobalLock)
{
- Dbg.Assert(_beginApplicationNotifyCount > 0, "Not running an executable - NotifyBeginApplication was not called!");
- --_beginApplicationNotifyCount;
- if (_beginApplicationNotifyCount == 0)
+ if (--_beginApplicationNotifyCount == 0)
{
- // restore the window title when the last application started has ended.
-
+ // Restore the window title when the last application started has ended.
ui.RawUI.WindowTitle = _savedWindowTitle;
#if !UNIX
if (_savedConsoleMode != ConsoleControl.ConsoleModes.Unknown)
{
ConsoleControl.SetMode(ConsoleControl.GetActiveScreenBufferHandle(), _savedConsoleMode);
+ if (_savedConsoleMode.HasFlag(ConsoleControl.ConsoleModes.VirtualTerminal))
+ {
+ // If the console output mode we just set already has 'VirtualTerminal' turned on,
+ // we don't need to try turn on the VT mode separately.
+ return;
+ }
+ }
+
+ if (ui.SupportsVirtualTerminal)
+ {
+ // Re-enable VT mode if it was previously enabled, as a native command may have turned it off.
+ ui.TryTurnOnVirtualTerminal();
}
#endif
}
@@ -2452,14 +2459,6 @@ internal void Run(bool inputLoopIsNested)
while (!_parent.ShouldEndSession && !_shouldExit)
{
-#if !UNIX
- if (ui.SupportsVirtualTerminal)
- {
- // need to re-enable VT mode if it was previously enabled as native commands may have turned it off
- ui.TryTurnOnVtMode();
- }
-#endif
-
try
{
_parent._isRunningPromptLoop = true;
diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs
index 12459ffa3b6..98abed42576 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs
@@ -87,11 +87,11 @@ internal ConsoleHostUserInterface(ConsoleHost parent)
if (SupportsVirtualTerminal)
{
- SupportsVirtualTerminal = TryTurnOnVtMode();
+ SupportsVirtualTerminal = TryTurnOnVirtualTerminal();
}
}
- internal bool TryTurnOnVtMode()
+ internal bool TryTurnOnVirtualTerminal()
{
#if UNIX
return true;
@@ -99,16 +99,22 @@ internal bool TryTurnOnVtMode()
try
{
// Turn on virtual terminal if possible.
-
// This might throw - not sure how exactly (no console), but if it does, we shouldn't fail to start.
- var handle = ConsoleControl.GetActiveScreenBufferHandle();
- var m = ConsoleControl.GetMode(handle);
- if (ConsoleControl.NativeMethods.SetConsoleMode(handle.DangerousGetHandle(), (uint)(m | ConsoleControl.ConsoleModes.VirtualTerminal)))
+ var outputHandle = ConsoleControl.GetActiveScreenBufferHandle();
+ var outputMode = ConsoleControl.GetMode(outputHandle);
+
+ if (outputMode.HasFlag(ConsoleControl.ConsoleModes.VirtualTerminal))
+ {
+ return true;
+ }
+
+ outputMode |= ConsoleControl.ConsoleModes.VirtualTerminal;
+ if (ConsoleControl.NativeMethods.SetConsoleMode(outputHandle.DangerousGetHandle(), (uint)outputMode))
{
// We only know if vt100 is supported if the previous call actually set the new flag, older
// systems ignore the setting.
- m = ConsoleControl.GetMode(handle);
- return (m & ConsoleControl.ConsoleModes.VirtualTerminal) != 0;
+ outputMode = ConsoleControl.GetMode(outputHandle);
+ return outputMode.HasFlag(ConsoleControl.ConsoleModes.VirtualTerminal);
}
}
catch
diff --git a/src/System.Management.Automation/engine/NativeCommandProcessor.cs b/src/System.Management.Automation/engine/NativeCommandProcessor.cs
index 12c02e60783..efc8b660c37 100644
--- a/src/System.Management.Automation/engine/NativeCommandProcessor.cs
+++ b/src/System.Management.Automation/engine/NativeCommandProcessor.cs
@@ -545,15 +545,14 @@ private void InitNativeProcess()
Exception exceptionToRethrow = null;
try
{
- // If this process is being run standalone, tell the host, which might want
- // to save off the window title or other such state as might be tweaked by
- // the native process
+ // Before start the executable, tell the host, which might want to save off the
+ // window title or other such state as might be tweaked by the native process.
+ Command.Context.EngineHostInterface.NotifyBeginApplication();
+ _hasNotifiedBeginApplication = true;
+
if (_runStandAlone)
{
- this.Command.Context.EngineHostInterface.NotifyBeginApplication();
- _hasNotifiedBeginApplication = true;
-
- // Also, store the Raw UI coordinates so that we can scrape the screen after
+ // Store the Raw UI coordinates so that we can scrape the screen after
// if we are transcribing.
if (_isTranscribing && (s_supportScreenScrape == true))
{
@@ -1166,7 +1165,7 @@ private void CleanUp()
// We need to call 'NotifyEndApplication' as appropriate during cleanup
if (_hasNotifiedBeginApplication)
{
- this.Command.Context.EngineHostInterface.NotifyEndApplication();
+ Command.Context.EngineHostInterface.NotifyEndApplication();
}
try
From 8f0fc9e43a84647bdb7a1b637fd39eac760fdb4a Mon Sep 17 00:00:00 2001
From: Dongbo Wang
Date: Tue, 18 Jan 2022 15:58:11 -0800
Subject: [PATCH 015/209] Replace the broken link about pull request (#16771)
---
docs/maintainers/README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/maintainers/README.md b/docs/maintainers/README.md
index 0c28e2cc9de..e592661cf8f 100644
--- a/docs/maintainers/README.md
+++ b/docs/maintainers/README.md
@@ -6,8 +6,8 @@ One of their primary responsibilities is merging pull requests after all require
They have [write access](https://docs.github.com/en/free-pro-team@latest/github/setting-up-and-managing-organizations-and-teams/repository-permission-levels-for-an-organization) to the PowerShell repositories which gives them the power to:
1. `git push` to the official PowerShell repository
-1. Merge [pull requests](https://www.thinkful.com/learn/github-pull-request-tutorial/)
-1. Assign labels, milestones, and people to [issues](https://guides.github.com/features/issues/) and [pull requests](https://www.thinkful.com/learn/github-pull-request-tutorial/)
+1. Merge [pull requests](https://docs.github.com/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests)
+1. Assign labels, milestones, and people to [issues](https://guides.github.com/features/issues/) and [pull requests](https://docs.github.com/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests)
## Table of Contents
From ee9097602d1778c62f3d71fa9da456a15987790d Mon Sep 17 00:00:00 2001
From: Dongbo Wang
Date: Thu, 20 Jan 2022 12:28:41 -0800
Subject: [PATCH 016/209] Update Engine Working Group members (#16780)
---
docs/community/working-group-definitions.md | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/docs/community/working-group-definitions.md b/docs/community/working-group-definitions.md
index f3efc89df3c..d02810078e6 100644
--- a/docs/community/working-group-definitions.md
+++ b/docs/community/working-group-definitions.md
@@ -33,7 +33,6 @@ Special consideration should be given to topics like **backwards compatibility**
* @JamesWTruher (PS Standard, module authoring)
* @adityapatwardhan (SDK)
-* @rjmholt (hosting, WinPS compatibility)
## Engine
@@ -60,9 +59,9 @@ However, it's expected that many issues will require input from both WGs.
* @BrucePay
* @daxian-dbw
* @JamesWTruher
-* @rjmholt
* @rkeithhill
* @vexx32
+* @SeeminglyScience
## Interactive UX
@@ -94,7 +93,6 @@ particularly given the long-lasting effects of language decisions.
### Members
* @JamesWTruher
-* @rjmholt
* @daxian-dbw
* @BrucePay
From 2c2722984d9d72a86bfe00c9dca2ccdeff319594 Mon Sep 17 00:00:00 2001
From: Aditya Patwardhan
Date: Thu, 20 Jan 2022 15:15:43 -0800
Subject: [PATCH 017/209] Update the expect .NET SDK quality to GA for
installing dotnet (#16784)
---
DotnetRuntimeMetadata.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/DotnetRuntimeMetadata.json b/DotnetRuntimeMetadata.json
index 96adaddc003..0917e8476c6 100644
--- a/DotnetRuntimeMetadata.json
+++ b/DotnetRuntimeMetadata.json
@@ -1,7 +1,7 @@
{
"sdk": {
"channel": "6.0.1xx",
- "quality": "signed",
+ "quality": "ga",
"qualityFallback": "daily",
"packageVersionPattern": "6.0.0",
"sdkImageVersion": "6.0.100",
From 3eca7c019ee48aa038272aa905cdd364aec1a04b Mon Sep 17 00:00:00 2001
From: hammy3502
Date: Thu, 20 Jan 2022 23:23:11 -0500
Subject: [PATCH 018/209] Adds a .ResolvedTarget Property to File-System Items
to Reflect a Symlink's Target as FileSystemInfo (#16490)
---
.../engine/TypeTable_Types_Ps1Xml.cs | 22 ++++++++
.../namespaces/FileSystemProvider.cs | 16 ++++++
.../FileSystemProviderExtended.Tests.ps1 | 54 +++++++++++++++++++
.../engine/Api/TypeInference.Tests.ps1 | 2 +-
4 files changed, 93 insertions(+), 1 deletion(-)
diff --git a/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs b/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs
index 648bafe152c..bb267267b31 100644
--- a/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs
+++ b/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs
@@ -676,6 +676,17 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag errors)
typeMembers,
isOverride: false);
+ newMembers.Add(@"ResolvedTarget");
+ AddMember(
+ errors,
+ typeName,
+ new PSCodeProperty(
+ @"ResolvedTarget",
+ GetMethodInfo(typeof(Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods), @"ResolvedTarget"),
+ setterCodeReference: null),
+ typeMembers,
+ isOverride: false);
+
newMembers.Add(@"Target");
AddMember(
errors,
@@ -801,6 +812,17 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag errors)
typeMembers,
isOverride: false);
+ newMembers.Add(@"ResolvedTarget");
+ AddMember(
+ errors,
+ typeName,
+ new PSCodeProperty(
+ @"ResolvedTarget",
+ GetMethodInfo(typeof(Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods), @"ResolvedTarget"),
+ setterCodeReference: null),
+ typeMembers,
+ isOverride: false);
+
newMembers.Add(@"Target");
AddMember(
errors,
diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs
index 1e3ab21ccaf..c57ed5ed2dc 100644
--- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs
+++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs
@@ -8131,6 +8131,22 @@ public static string GetTarget(PSObject instance)
return null;
}
+ ///
+ /// Gets the target for a given file or directory, resolving symbolic links.
+ ///
+ /// The FileInfo or DirectoryInfo type.
+ /// The file path the instance points to.
+ public static string ResolvedTarget(PSObject instance)
+ {
+ if (instance.BaseObject is FileSystemInfo fileSysInfo)
+ {
+ FileSystemInfo linkTarget = fileSysInfo.ResolveLinkTarget(true);
+ return linkTarget is null ? fileSysInfo.FullName : linkTarget.FullName;
+ }
+
+ return null;
+ }
+
///
/// Gets the link type of the specified reparse point.
///
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystemProviderExtended.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystemProviderExtended.Tests.ps1
index e9c490d5392..199210cba6e 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystemProviderExtended.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystemProviderExtended.Tests.ps1
@@ -658,3 +658,57 @@ Describe "FileSystem Provider Extended Tests for Get-ChildItem cmdlet" -Tags "CI
}
}
}
+
+Describe "Validate Get-Item ResolvedTarget property" -Tag RequireAdminOnWindows {
+ BeforeAll {
+ $rootDir = Join-Path "TestDrive:" "TestDir"
+
+ Push-Location $rootDir
+ $null = New-Item -Path "realDir" -ItemType Directory
+ $null = New-Item -Path "toDel" -ItemType Directory
+ $null = New-Item -Path "brokenLinkedDir" -ItemType SymbolicLink -Value ".\toDel"
+ $null = New-Item -Path "linkedDir" -ItemType SymbolicLink -Value ".\realDir"
+ Remove-Item "toDel"
+ $null = New-Item -Path "realFile.fil" -ItemType File
+ $null = New-Item -Path "toDel.fil" -ItemType File
+ $null = New-Item -Path "brokenLinkedFile.fil" -ItemType SymbolicLink -Value ".\toDel.fil"
+ $null = New-Item -Path "linkedFile.fil" -ItemType SymbolicLink -Value ".\realFile.fil"
+ Remove-Item "toDel.fil"
+ }
+
+ AfterAll {
+ Pop-Location
+ }
+
+ Context 'Get-Item files and folders' {
+ It 'Get-Item "linkedDir"' {
+ $result = Get-Item "linkedDir"
+ $result.ResolvedTarget.EndsWith("realDir") | Should -BeTrue
+ }
+
+ It 'Get-Item "linkedFile.fil"' {
+ $result = Get-Item "linkedFile.fil"
+ $result.ResolvedTarget.EndsWith("realFile.fil") | Should -BeTrue
+ }
+
+ It 'Get-Item "brokenLinkedDir"' {
+ $result = Get-Item "brokenLinkedDir"
+ $result.ResolvedTarget.EndsWith("toDel") | Should -BeTrue
+ }
+
+ It 'Get-Item "brokenLinkedFile.fil"' {
+ $result = Get-Item "brokenLinkedFile.fil"
+ $result.ResolvedTarget.EndsWith("toDel.fil") | Should -BeTrue
+ }
+
+ It 'Get-Item "realDir"' {
+ $result = Get-Item "realDir"
+ $result.ResolvedTarget.EndsWith("realDir") | Should -BeTrue
+ }
+
+ It 'Get-Item "realFile.fil' {
+ $result = Get-Item "realFile.fil"
+ $result.ResolvedTarget.EndsWith("realFile.fil") | Should -BeTrue
+ }
+ }
+}
diff --git a/test/powershell/engine/Api/TypeInference.Tests.ps1 b/test/powershell/engine/Api/TypeInference.Tests.ps1
index 1d65047c34c..56c4ad074bc 100644
--- a/test/powershell/engine/Api/TypeInference.Tests.ps1
+++ b/test/powershell/engine/Api/TypeInference.Tests.ps1
@@ -504,7 +504,7 @@ Describe "Type inference Tests" -tags "CI" {
It "Infers typeof Select-Object when Parameter is ExcludeProperty" {
$res = [AstTypeInference]::InferTypeOf( { [io.fileinfo]::new("file") | Select-Object -ExcludeProperty *Time*, E* }.Ast)
$res.Count | Should -Be 1
- $res[0].Name | Should -BeExactly "System.Management.Automation.PSObject#Attributes:BaseName:Directory:DirectoryName:FullName:IsReadOnly:Length:LengthString:LinkTarget:LinkType:Mode:ModeWithoutHardLink:Name:NameString:Target:VersionInfo"
+ $res[0].Name | Should -BeExactly "System.Management.Automation.PSObject#Attributes:BaseName:Directory:DirectoryName:FullName:IsReadOnly:Length:LengthString:LinkTarget:LinkType:Mode:ModeWithoutHardLink:Name:NameString:ResolvedTarget:Target:VersionInfo"
$names = $res[0].Members.Name
$names -contains "BaseName" | Should -BeTrue
$names -contains "Name" | Should -BeTrue
From f83660e0e6143ce702b1e009fa62c9869cb5dac4 Mon Sep 17 00:00:00 2001
From: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
Date: Fri, 21 Jan 2022 13:54:16 +0000
Subject: [PATCH 019/209] Seal `ClientRemotePowerShell` (#15802)
Seal `System.Management.Automation.Runspaces.Internal.ClientRemotePowerShell
Simplify IDisposable implementation
---
.../remoting/client/ClientRemotePowerShell.cs | 69 ++++++++-----------
1 file changed, 28 insertions(+), 41 deletions(-)
diff --git a/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs b/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs
index 4ea06ea2feb..b464028d851 100644
--- a/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs
+++ b/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs
@@ -15,7 +15,7 @@ namespace System.Management.Automation.Runspaces.Internal
/// PowerShell client side proxy base which handles invocation
/// of powershell on a remote machine.
///
- internal class ClientRemotePowerShell : IDisposable
+ internal sealed class ClientRemotePowerShell : IDisposable
{
#region Tracer
@@ -902,28 +902,28 @@ private void HandleRobustConnectionNotification(
#endregion Private Methods
- #region Protected Members
-
- protected ObjectStreamBase inputstream;
- protected ObjectStreamBase errorstream;
- protected PSInformationalBuffers informationalBuffers;
- protected PowerShell shell;
- protected Guid clientRunspacePoolId;
- protected bool noInput;
- protected PSInvocationSettings settings;
- protected ObjectStreamBase outputstream;
- protected string computerName;
- protected ClientPowerShellDataStructureHandler dataStructureHandler;
- protected bool stopCalled = false;
- protected PSHost hostToUse;
- protected RemoteRunspacePoolInternal runspacePool;
-
- protected const string WRITE_DEBUG_LINE = "WriteDebugLine";
- protected const string WRITE_VERBOSE_LINE = "WriteVerboseLine";
- protected const string WRITE_WARNING_LINE = "WriteWarningLine";
- protected const string WRITE_PROGRESS = "WriteProgress";
-
- protected bool initialized = false;
+ #region Private Fields
+
+ private ObjectStreamBase inputstream;
+ private ObjectStreamBase errorstream;
+ private PSInformationalBuffers informationalBuffers;
+ private readonly PowerShell shell;
+ private readonly Guid clientRunspacePoolId;
+ private bool noInput;
+ private PSInvocationSettings settings;
+ private ObjectStreamBase outputstream;
+ private readonly string computerName;
+ private ClientPowerShellDataStructureHandler dataStructureHandler;
+ private bool stopCalled = false;
+ private PSHost hostToUse;
+ private readonly RemoteRunspacePoolInternal runspacePool;
+
+ private const string WRITE_DEBUG_LINE = "WriteDebugLine";
+ private const string WRITE_VERBOSE_LINE = "WriteVerboseLine";
+ private const string WRITE_WARNING_LINE = "WriteWarningLine";
+ private const string WRITE_PROGRESS = "WriteProgress";
+
+ private bool initialized = false;
///
/// This queue is for the state change events that resulted in closing the underlying
/// datastructure handler. We cannot send the state back to the upper layers until
@@ -933,33 +933,20 @@ private void HandleRobustConnectionNotification(
private PSConnectionRetryStatus _connectionRetryStatus = PSConnectionRetryStatus.None;
- #endregion Protected Members
+ #endregion Private Fields
#region IDisposable
///
- /// Public interface for dispose.
+ /// Release all resources.
///
public void Dispose()
{
- Dispose(true);
-
- GC.SuppressFinalize(this);
+ // inputstream.Dispose();
+ // outputstream.Dispose();
+ // errorstream.Dispose();
}
- ///
- /// Release all resources.
- ///
- /// If true, release all managed resources.
- protected void Dispose(bool disposing)
- {
- if (disposing)
- {
- // inputstream.Dispose();
- // outputstream.Dispose();
- // errorstream.Dispose();
- }
- }
#endregion IDisposable
}
From 0302b1f48e250c9387f976b78c9f4b6434ac9420 Mon Sep 17 00:00:00 2001
From: Brannen Hall
Date: Sat, 22 Jan 2022 00:59:58 -0600
Subject: [PATCH 020/209] Support OpenSSH options for PSRP over SSH commands
(#12802)
Add an -Options parameter to Invoke-Command and Start-PSSession to pass SSH options
Options parameter added to Invoke-Command
Options parameter added to Start-PSSession
Options parameter added to Enter-PSSession
Options parameter added to SSHConnection Hashtable
---
.../remoting/commands/InvokeCommandCommand.cs | 19 +++++++
.../remoting/commands/PSRemotingCmdlet.cs | 15 +++++-
.../remoting/commands/PushRunspaceCommand.cs | 20 ++++++-
.../remoting/commands/newrunspacecommand.cs | 6 ++-
.../remoting/common/RunspaceConnectionInfo.cs | 54 ++++++++++++++++---
test/SSHRemoting/SSHRemoting.Basic.Tests.ps1 | 7 +++
6 files changed, 111 insertions(+), 10 deletions(-)
diff --git a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs
index a3aca6de70c..c194706151d 100644
--- a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs
+++ b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs
@@ -858,6 +858,25 @@ public override Hashtable[] SSHConnection
set;
}
+ ///
+ /// Hashtable containing options to be passed to OpenSSH.
+ ///
+ [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)]
+ [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)]
+ [ValidateNotNullOrEmpty]
+ public override Hashtable Options
+ {
+ get
+ {
+ return base.Options;
+ }
+
+ set
+ {
+ base.Options = value;
+ }
+ }
+
#endregion
#region Remote Debug Parameters
diff --git a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs
index 67d0ced9f15..a3475bf9465 100644
--- a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs
+++ b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs
@@ -287,6 +287,7 @@ internal struct SSHConnection
public int Port;
public string Subsystem;
public int ConnectingTimeout;
+ public Hashtable Options;
}
///
@@ -805,6 +806,13 @@ public virtual Hashtable[] SSHConnection
set;
}
+ ///
+ /// Gets or sets the Hashtable containing options to be passed to OpenSSH.
+ ///
+ [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)]
+ [ValidateNotNullOrEmpty]
+ public virtual Hashtable Options { get; set; }
+
#endregion
#endregion Properties
@@ -866,6 +874,7 @@ internal static void ValidateSpecifiedAuthentication(PSCredential credential, st
private const string PortParameter = "Port";
private const string SubsystemParameter = "Subsystem";
private const string ConnectingTimeoutParameter = "ConnectingTimeout";
+ private const string OptionsParameter = "Options";
#endregion
@@ -969,6 +978,10 @@ internal SSHConnection[] ParseSSHConnectionHashTable()
{
connectionInfo.ConnectingTimeout = GetSSHConnectionIntParameter(item[paramName]);
}
+ else if (paramName.Equals(OptionsParameter, StringComparison.OrdinalIgnoreCase))
+ {
+ connectionInfo.Options = item[paramName] as Hashtable;
+ }
else
{
throw new PSArgumentException(
@@ -1462,7 +1475,7 @@ protected void CreateHelpersForSpecifiedSSHComputerNames()
{
ParseSshHostName(computerName, out string host, out string userName, out int port);
- var sshConnectionInfo = new SSHConnectionInfo(userName, host, KeyFilePath, port, Subsystem, ConnectingTimeout);
+ var sshConnectionInfo = new SSHConnectionInfo(userName, host, KeyFilePath, port, Subsystem, ConnectingTimeout, Options);
var typeTable = TypeTable.LoadDefaultTypeFiles();
var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, Host, typeTable) as RemoteRunspace;
var pipeline = CreatePipeline(remoteRunspace);
diff --git a/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs b/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs
index 996b043d77e..bf8041d2e48 100644
--- a/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs
+++ b/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs
@@ -58,6 +58,24 @@ public class EnterPSSessionCommand : PSRemotingBaseCmdlet
[ValidateNotNullOrEmpty()]
public new string HostName { get; set; }
+ ///
+ /// Gets or sets the Hashtable containing options to be passed to OpenSSH.
+ ///
+ [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
+ [ValidateNotNullOrEmpty]
+ public override Hashtable Options
+ {
+ get
+ {
+ return base.Options;
+ }
+
+ set
+ {
+ base.Options = value;
+ }
+ }
+
#endregion
///
@@ -1262,7 +1280,7 @@ private RemoteRunspace GetRunspaceForContainerSession()
private RemoteRunspace GetRunspaceForSSHSession()
{
ParseSshHostName(HostName, out string host, out string userName, out int port);
- var sshConnectionInfo = new SSHConnectionInfo(userName, host, KeyFilePath, port, Subsystem, ConnectingTimeout);
+ var sshConnectionInfo = new SSHConnectionInfo(userName, host, KeyFilePath, port, Subsystem, ConnectingTimeout, Options);
var typeTable = TypeTable.LoadDefaultTypeFiles();
// Use the class _tempRunspace field while the runspace is being opened so that StopProcessing can be handled at that time.
diff --git a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs
index d4a0a3e7508..a9ba8007a2b 100644
--- a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs
+++ b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs
@@ -1093,7 +1093,8 @@ private List CreateRunspacesForSSHHostParameterSet()
this.KeyFilePath,
port,
Subsystem,
- ConnectingTimeout);
+ ConnectingTimeout,
+ Options);
var typeTable = TypeTable.LoadDefaultTypeFiles();
string rsName = GetRunspaceName(index, out int rsIdUnused);
index++;
@@ -1120,7 +1121,8 @@ private List CreateRunspacesForSSHHostHashParameterSet()
sshConnection.KeyFilePath,
sshConnection.Port,
sshConnection.Subsystem,
- sshConnection.ConnectingTimeout);
+ sshConnection.ConnectingTimeout,
+ sshConnection.Options);
var typeTable = TypeTable.LoadDefaultTypeFiles();
string rsName = GetRunspaceName(index, out int rsIdUnused);
index++;
diff --git a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs
index 49763f43a13..1a28cc75980 100644
--- a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs
+++ b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System.Collections;
using System.Collections.Generic;
using System.ComponentModel; // Win32Exception
using System.Diagnostics;
@@ -1969,18 +1970,27 @@ public int ConnectingTimeout
set;
}
+ /// The SSH options to pass to OpenSSH.
+ /// Gets or sets the SSH options to pass to OpenSSH.
+ ///
+ private Hashtable Options
+ {
+ get;
+ set;
+ }
+
#endregion
#region Constructors
///
- /// Constructor.
+ /// Initializes a new instance of the class.
///
private SSHConnectionInfo()
{ }
///
- /// Constructor.
+ /// Initializes a new instance of the class.
///
/// User Name.
/// Computer Name.
@@ -2001,7 +2011,7 @@ public SSHConnectionInfo(
}
///
- /// Constructor.
+ /// Initializes a new instance of the class.
///
/// User Name.
/// Computer Name.
@@ -2018,7 +2028,7 @@ public SSHConnectionInfo(
}
///
- /// Constructor.
+ /// Initializes a new instance of the class.
///
/// User Name.
/// Computer Name.
@@ -2055,6 +2065,28 @@ public SSHConnectionInfo(
ConnectingTimeout = connectingTimeout;
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// User Name.
+ /// Computer Name.
+ /// Key File Path.
+ /// Port number for connection (default 22).
+ /// Subsystem to use (default 'powershell').
+ /// Timeout time for terminating connection attempt.
+ /// Options for the SSH connection.
+ public SSHConnectionInfo(
+ string userName,
+ string computerName,
+ string keyFilePath,
+ int port,
+ string subsystem,
+ int connectingTimeout,
+ Hashtable options) : this(userName, computerName, keyFilePath, port, subsystem, connectingTimeout)
+ {
+ Options = options;
+ }
+
#endregion
#region Overrides
@@ -2111,6 +2143,7 @@ internal override RunspaceConnectionInfo InternalCopy()
newCopy.Port = Port;
newCopy.Subsystem = Subsystem;
newCopy.ConnectingTimeout = ConnectingTimeout;
+ newCopy.Options = Options;
return newCopy;
}
@@ -2163,9 +2196,9 @@ internal int StartSSHProcess(
//
// Local ssh invoked as:
// windows:
- // ssh.exe [-i identity_file] [-l login_name] [-p port] -s
+ // ssh.exe [-i identity_file] [-l login_name] [-p port] [-o option] -s
// linux|macos:
- // ssh [-i identity_file] [-l login_name] [-p port] -s
+ // ssh [-i identity_file] [-l login_name] [-p port] [-o option] -s
// where is interpreted as the subsystem due to the -s flag.
//
// Remote sshd configured for PowerShell Remoting Protocol (PSRP) over Secure Shell Protocol (SSH)
@@ -2216,6 +2249,15 @@ internal int StartSSHProcess(
startInfo.ArgumentList.Add(string.Format(CultureInfo.InvariantCulture, @"-p {0}", this.Port));
}
+ // pass "-o option=value" command line argument to ssh if options are provided
+ if (this.Options != null)
+ {
+ foreach (DictionaryEntry pair in this.Options)
+ {
+ startInfo.ArgumentList.Add(string.Format(CultureInfo.InvariantCulture, @"-o {0}={1}", pair.Key, pair.Value));
+ }
+ }
+
// pass "-s destination command" command line arguments to ssh where command is the subsystem to invoke on the destination
// note that ssh expects IPv6 addresses to not be enclosed in square brackets so trim them if present
startInfo.ArgumentList.Add(string.Format(CultureInfo.InvariantCulture, @"-s {0} {1}", this.ComputerName.TrimStart('[').TrimEnd(']'), this.Subsystem));
diff --git a/test/SSHRemoting/SSHRemoting.Basic.Tests.ps1 b/test/SSHRemoting/SSHRemoting.Basic.Tests.ps1
index 7383cc2b8a1..29e7096d49a 100644
--- a/test/SSHRemoting/SSHRemoting.Basic.Tests.ps1
+++ b/test/SSHRemoting/SSHRemoting.Basic.Tests.ps1
@@ -179,6 +179,13 @@ Describe "SSHRemoting Basic Tests" -tags CI {
Write-Verbose -Verbose "It Complete"
}
+ It "Verifies explicit Options parameter" {
+ $options = @{"Port"="22"}
+ $script:session = New-PSSession -HostName localhost -Options $options -ErrorVariable err
+ $err | Should -HaveCount 0
+ VerifySession $script:session
+ }
+
It "Verifies explicit Subsystem parameter" {
Write-Verbose -Verbose "It Starting: Verifies explicit Subsystem parameter"
$portNum = 22
From ae38dad04ffeff01f069b0b10edb8225fed10cff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hong=20Minhee=20=28=E6=B4=AA=20=E6=B0=91=E6=86=99=29?=
Date: Tue, 25 Jan 2022 02:37:56 +0900
Subject: [PATCH 021/209] Let macOS installer run without Rosetta on Apple
Silicon (#16742)
---
tools/packaging/packaging.psm1 | 47 ++++++++++++++++++++++++--
tools/packaging/packaging.strings.psd1 | 2 +-
2 files changed, 46 insertions(+), 3 deletions(-)
diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1
index bca41d3c816..39028c2673c 100644
--- a/tools/packaging/packaging.psm1
+++ b/tools/packaging/packaging.psm1
@@ -504,6 +504,29 @@ function Start-PSPackage {
}
}
}
+ 'osxpkg' {
+ $HostArchitecture = "x86_64"
+ if ($MacOSRuntime -match "-arm64") {
+ $HostArchitecture = "arm64"
+ }
+ Write-Verbose "HostArchitecture = $HostArchitecture" -Verbose
+
+ $Arguments = @{
+ Type = 'osxpkg'
+ PackageSourcePath = $Source
+ Name = $Name
+ Version = $Version
+ Force = $Force
+ NoSudo = $NoSudo
+ LTS = $LTS
+ HostArchitecture = $HostArchitecture
+ }
+
+
+ if ($PSCmdlet.ShouldProcess("Create macOS Package")) {
+ New-UnixPackage @Arguments
+ }
+ }
default {
$Arguments = @{
Type = $_
@@ -803,6 +826,19 @@ function New-UnixPackage {
$Dict = New-Object "System.Management.Automation.RuntimeDefinedParameterDictionary"
$Dict.Add("Distribution", $Parameter) > $null
return $Dict
+ } elseif ($Type -eq "osxpkg") {
+ # Add a dynamic parameter '-HostArchitecture' when the specified package type is 'osxpkg'.
+ # The '-HostArchitecture' parameter is used to indicate which Mac processor this package is targeting,
+ # Intel (x86_64) or Apple Silicon (arm64).
+ $ParameterAttr = New-Object "System.Management.Automation.ParameterAttribute"
+ $ValidateSetAttr = New-Object "System.Management.Automation.ValidateSetAttribute" -ArgumentList "x86_64", "arm64"
+ $Attributes = New-Object "System.Collections.ObjectModel.Collection``1[System.Attribute]"
+ $Attributes.Add($ParameterAttr) > $null
+ $Attributes.Add($ValidateSetAttr) > $null
+ $Parameter = New-Object "System.Management.Automation.RuntimeDefinedParameter" -ArgumentList ("HostArchitecture", [string], $Attributes)
+ $Dict = New-Object "System.Management.Automation.RuntimeDefinedParameterDictionary"
+ $Dict.Add("HostArchitecture", $Parameter) > $null
+ return $Dict
}
}
@@ -856,6 +892,7 @@ function New-UnixPackage {
throw ($ErrorMessage -f "macOS")
}
+ $HostArchitecture = $PSBoundParameters['HostArchitecture']
$DebDistro = 'macOS'
}
}
@@ -1013,7 +1050,7 @@ function New-UnixPackage {
if ($Environment.IsMacOS) {
if ($PSCmdlet.ShouldProcess("Add distribution information and Fix PackageName"))
{
- $createdPackage = New-MacOsDistributionPackage -FpmPackage $createdPackage -IsPreview:$IsPreview
+ $createdPackage = New-MacOsDistributionPackage -FpmPackage $createdPackage -HostArchitecture $HostArchitecture -IsPreview:$IsPreview
}
}
@@ -1063,6 +1100,11 @@ function New-MacOsDistributionPackage
param(
[Parameter(Mandatory,HelpMessage='The FileInfo of the file created by FPM')]
[System.IO.FileInfo]$FpmPackage,
+
+ [Parameter(HelpMessage='x86_64 for Intel or arm64 for Apple Silicon')]
+ [ValidateSet("x86_64", "arm64")]
+ [string] $HostArchitecture = "x86_64",
+
[Switch] $IsPreview
)
@@ -1112,7 +1154,8 @@ function New-MacOsDistributionPackage
# 2 - package path
# 3 - minimum os version
# 4 - Package Identifier
- $PackagingStrings.OsxDistributionTemplate -f "PowerShell - $packageVersion", $packageVersion, $packageName, '10.14', $packageId | Out-File -Encoding ascii -FilePath $distributionXmlPath -Force
+ # 5 - host architecture (x86_64 for Intel or arm64 for Apple Silicon)
+ $PackagingStrings.OsxDistributionTemplate -f "PowerShell - $packageVersion", $packageVersion, $packageName, '10.14', $packageId, $HostArchitecture | Out-File -Encoding ascii -FilePath $distributionXmlPath -Force
Write-Log "Applying distribution.xml to package..."
Push-Location $tempDir
diff --git a/tools/packaging/packaging.strings.psd1 b/tools/packaging/packaging.strings.psd1
index 75068371efa..b70592c313d 100644
--- a/tools/packaging/packaging.strings.psd1
+++ b/tools/packaging/packaging.strings.psd1
@@ -125,7 +125,7 @@ open {0}
{0}
-
+
From 0c6005637c8752379831dc1e224bab2f1586b292 Mon Sep 17 00:00:00 2001
From: Paul Higinbotham
Date: Tue, 25 Jan 2022 14:05:11 -0800
Subject: [PATCH 022/209] Fix build for new `InvokeCommand` attributes (#16800)
---
tools/packaging/packaging.psm1 | 2 ++
1 file changed, 2 insertions(+)
diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1
index 39028c2673c..7b8c18b9e31 100644
--- a/tools/packaging/packaging.psm1
+++ b/tools/packaging/packaging.psm1
@@ -2337,6 +2337,8 @@ function CleanupGeneratedSourceCode
'[Microsoft.PowerShell.Commands.SetStrictModeCommand.ArgumentToPSVersionTransformationAttribute]'
'[Microsoft.PowerShell.Commands.HttpVersionCompletionsAttribute]'
'[System.Management.Automation.ArgumentToVersionTransformationAttribute]'
+ '[Microsoft.PowerShell.Commands.InvokeCommandCommand.ArgumentToPSVersionTransformationAttribute]'
+ '[Microsoft.PowerShell.Commands.InvokeCommandCommand.ValidateVersionAttribute]'
)
$patternsToReplace = @(
From 5e005b8a264686f1952e7912d10e792c2b70730a Mon Sep 17 00:00:00 2001
From: Travis Plunk
Date: Wed, 26 Jan 2022 11:20:08 -0800
Subject: [PATCH 023/209] Remove all references to `cmake` for the builds in
this repo (#16578)
---
build.psm1 | 15 ++++-----------
docs/building/linux.md | 4 +---
docs/building/macos.md | 2 +-
test/perf/benchmarks/assets/compiler.test.ps1 | 11 ++++-------
.../microsoft_powershell_alpine3/Dockerfile | 14 --------------
5 files changed, 10 insertions(+), 36 deletions(-)
delete mode 100644 tools/releaseBuild/Images/microsoft_powershell_alpine3/Dockerfile
diff --git a/build.psm1 b/build.psm1
index 4330011863e..f32382ad51e 100644
--- a/build.psm1
+++ b/build.psm1
@@ -1930,11 +1930,7 @@ function Start-PSBootstrap {
$Deps = @()
if ($environment.IsLinux -and $environment.IsUbuntu) {
# Build tools
- $Deps += "curl", "g++", "cmake", "make"
-
- if ($BuildLinuxArm) {
- $Deps += "gcc-arm-linux-gnueabihf", "g++-arm-linux-gnueabihf"
- }
+ $Deps += "curl", "wget"
# .NET Core required runtime libraries
$Deps += "libunwind8"
@@ -1960,7 +1956,7 @@ function Start-PSBootstrap {
}
} elseif ($environment.IsLinux -and $environment.IsRedHatFamily) {
# Build tools
- $Deps += "which", "curl", "gcc-c++", "cmake", "make"
+ $Deps += "which", "curl", "wget"
# .NET Core required runtime libraries
$Deps += "libicu", "libunwind"
@@ -1984,7 +1980,7 @@ function Start-PSBootstrap {
}
} elseif ($environment.IsLinux -and $environment.IsSUSEFamily) {
# Build tools
- $Deps += "gcc", "cmake", "make"
+ $Deps += "wget"
# Packaging tools
if ($Package) { $Deps += "ruby-devel", "rpmbuild", "groff", 'libffi-devel' }
@@ -2009,9 +2005,6 @@ function Start-PSBootstrap {
$PackageManager = "$sudo port"
}
- # Build tools
- $Deps += "cmake"
-
# wget for downloading dotnet
$Deps += "wget"
@@ -2022,7 +2015,7 @@ function Start-PSBootstrap {
# ignore exitcode, because they may be already installed
Start-NativeExecution ([ScriptBlock]::Create("$PackageManager install $Deps")) -IgnoreExitcode
} elseif ($environment.IsLinux -and $environment.IsAlpine) {
- $Deps += 'libunwind', 'libcurl', 'bash', 'cmake', 'clang', 'build-base', 'git', 'curl', 'wget'
+ $Deps += 'libunwind', 'libcurl', 'bash', 'build-base', 'git', 'curl', 'wget'
Start-NativeExecution {
Invoke-Expression "apk add $Deps"
diff --git a/docs/building/linux.md b/docs/building/linux.md
index fdf355fcde1..658a0250fcf 100644
--- a/docs/building/linux.md
+++ b/docs/building/linux.md
@@ -18,8 +18,7 @@ refer to the [Working with the PowerShell Repository](../git/README.md),
### Toolchain Setup
-We use the [.NET Command-Line Interface][dotnet-cli] (`dotnet`) to build the managed components,
-and [CMake][] to build the native components.
+We use the [.NET Command-Line Interface][dotnet-cli] (`dotnet`) to build the managed components.
Installing the toolchain is as easy as running `Start-PSBootstrap` in PowerShell.
Of course, this requires a self-hosted copy of PowerShell on Linux.
@@ -55,7 +54,6 @@ The `Start-PSBootstrap` function does the following:
If you want to use `dotnet` outside of `Start-PSBuild`, add `~/.dotnet` to your `PATH` environment variable.
[dotnet-cli]: https://docs.microsoft.com/dotnet/core/tools/
-[CMake]: https://cmake.org/cmake/help/v2.8.12/cmake.html
## Build using our module
diff --git a/docs/building/macos.md b/docs/building/macos.md
index 8898a917877..63f1b4c9f82 100644
--- a/docs/building/macos.md
+++ b/docs/building/macos.md
@@ -14,7 +14,7 @@ From `pwsh.exe`, run `Import-Module ./build.psm1` and use `Start-PSBootstrap` to
The `Start-PSBootstrap` function does the following:
-- Uses `brew` or `port` to install CMake, OpenSSL, and GNU WGet
+- Uses `brew` or `port` to install OpenSSL, and GNU WGet
- Uninstalls any prior versions of .NET CLI
- Downloads and installs .NET Core SDK to `~/.dotnet`
diff --git a/test/perf/benchmarks/assets/compiler.test.ps1 b/test/perf/benchmarks/assets/compiler.test.ps1
index c7ca697f2cc..5105ae9b408 100644
--- a/test/perf/benchmarks/assets/compiler.test.ps1
+++ b/test/perf/benchmarks/assets/compiler.test.ps1
@@ -1149,7 +1149,7 @@ function Start-PSBootstrap {
$Deps = @()
if ($environment.IsLinux -and $environment.IsUbuntu) {
# Build tools
- $Deps += "curl", "g++", "cmake", "make"
+ $Deps += "curl", "g++", "make"
if ($BuildLinuxArm) {
$Deps += "gcc-arm-linux-gnueabihf", "g++-arm-linux-gnueabihf"
@@ -1179,7 +1179,7 @@ function Start-PSBootstrap {
}
} elseif ($environment.IsLinux -and $environment.IsRedHatFamily) {
# Build tools
- $Deps += "which", "curl", "gcc-c++", "cmake", "make"
+ $Deps += "which", "curl", "gcc-c++", "make"
# .NET Core required runtime libraries
$Deps += "libicu", "libunwind"
@@ -1203,7 +1203,7 @@ function Start-PSBootstrap {
}
} elseif ($environment.IsLinux -and $environment.IsSUSEFamily) {
# Build tools
- $Deps += "gcc", "cmake", "make"
+ $Deps += "gcc", "make"
# Packaging tools
if ($Package) { $Deps += "ruby-devel", "rpmbuild", "groff", 'libffi-devel' }
@@ -1228,9 +1228,6 @@ function Start-PSBootstrap {
$PackageManager = "$sudo port"
}
- # Build tools
- $Deps += "cmake"
-
# .NET Core required runtime libraries
$Deps += "openssl"
@@ -1238,7 +1235,7 @@ function Start-PSBootstrap {
# ignore exitcode, because they may be already installed
Start-NativeExecution ([ScriptBlock]::Create("$PackageManager install $Deps")) -IgnoreExitcode
} elseif ($environment.IsLinux -and $environment.IsAlpine) {
- $Deps += 'libunwind', 'libcurl', 'bash', 'cmake', 'clang', 'build-base', 'git', 'curl'
+ $Deps += 'libunwind', 'libcurl', 'bash', 'clang', 'build-base', 'git', 'curl'
Start-NativeExecution {
Invoke-Expression "apk add $Deps"
diff --git a/tools/releaseBuild/Images/microsoft_powershell_alpine3/Dockerfile b/tools/releaseBuild/Images/microsoft_powershell_alpine3/Dockerfile
deleted file mode 100644
index fb1070fcd52..00000000000
--- a/tools/releaseBuild/Images/microsoft_powershell_alpine3/Dockerfile
+++ /dev/null
@@ -1,14 +0,0 @@
-# Docker image file that describes an Centos7 image with PowerShell installed from Microsoft YUM Repo
-
-FROM mcr.microsoft.com/powershell:alpine-3.12
-LABEL maintainer="PowerShell Team "
-
-# Install dependencies and clean up
-RUN apk update \
- && apk add libunwind libcurl cmake clang build-base git bash curl
-
-COPY PowerShellPackage.ps1 /
-
-ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
-
-ENTRYPOINT [ "pwsh" ]
From d45ff6a20ab2bac98f1088b90284f8e24a8b5d40 Mon Sep 17 00:00:00 2001
From: Travis Plunk
Date: Wed, 26 Jan 2022 11:47:14 -0800
Subject: [PATCH 024/209] Switch to our custom images for build and release
(#16801)
---
build.psm1 | 2 +-
tools/packaging/packaging.psm1 | 14 +++++++++++---
tools/releaseBuild/azureDevOps/releaseBuild.yml | 2 +-
tools/releaseBuild/azureDevOps/releasePipeline.yml | 10 +++++-----
.../azureDevOps/templates/checkAzureContainer.yml | 2 +-
.../azureDevOps/templates/compliance.yml | 2 +-
.../templates/compliance/compliance.yml | 2 +-
.../templates/compliance/generateNotice.yml | 2 +-
tools/releaseBuild/azureDevOps/templates/json.yml | 2 +-
tools/releaseBuild/azureDevOps/templates/linux.yml | 4 ++--
.../azureDevOps/templates/mac-file-signing.yml | 2 +-
.../azureDevOps/templates/mac-package-signing.yml | 2 +-
tools/releaseBuild/azureDevOps/templates/nuget.yml | 2 +-
.../azureDevOps/templates/release-MsixBundle.yml | 2 +-
.../templates/release-UpdateDepsJson.yml | 2 +-
.../azureDevOps/templates/vpackReleaseJob.yml | 2 +-
.../templates/windows-component-governance.yml | 2 +-
.../azureDevOps/templates/windows-hosted-build.yml | 2 +-
.../templates/windows-package-signing.yml | 2 +-
.../azureDevOps/templates/windows-packaging.yml | 2 +-
tools/releaseBuild/azureDevOps/vpackRelease.yml | 2 +-
21 files changed, 36 insertions(+), 28 deletions(-)
diff --git a/build.psm1 b/build.psm1
index f32382ad51e..a4c16b852c2 100644
--- a/build.psm1
+++ b/build.psm1
@@ -1938,7 +1938,7 @@ function Start-PSBootstrap {
elseif ($environment.IsUbuntu18) { $Deps += "libicu60"}
# Packaging tools
- if ($Package) { $Deps += "ruby-dev", "groff", "libffi-dev" }
+ if ($Package) { $Deps += "ruby-dev", "groff", "libffi-dev", "rpm" }
# Install dependencies
# change the fontend from apt-get to noninteractive
diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1
index 7b8c18b9e31..e190e049e4c 100644
--- a/tools/packaging/packaging.psm1
+++ b/tools/packaging/packaging.psm1
@@ -1019,7 +1019,15 @@ function New-UnixPackage {
try {
if ($PSCmdlet.ShouldProcess("Create $type package")) {
Write-Log "Creating package with fpm..."
- $Output = Start-NativeExecution { fpm $Arguments }
+ try {
+ $Output = Start-NativeExecution { fpm $Arguments }
+ }
+ catch {
+ Write-Verbose -Message "!!!Handling error in FPM!!!" -Verbose -ErrorAction SilentlyContinue
+ Write-Verbose -Message "$Output" -Verbose -ErrorAction SilentlyContinue
+ Get-Error -InputObject $_
+ throw
+ }
}
} finally {
if ($Environment.IsMacOS) {
@@ -4149,7 +4157,7 @@ function Invoke-AzDevOpsLinuxPackageCreation {
}
}
catch {
- Get-Error
+ Get-Error -InputObject $_
throw
}
}
@@ -4230,7 +4238,7 @@ function Invoke-AzDevOpsLinuxPackageBuild {
}
}
catch {
- Get-Error
+ Get-Error -InputObject $_
throw
}
}
diff --git a/tools/releaseBuild/azureDevOps/releaseBuild.yml b/tools/releaseBuild/azureDevOps/releaseBuild.yml
index 49fcb751472..97cb0d02eac 100644
--- a/tools/releaseBuild/azureDevOps/releaseBuild.yml
+++ b/tools/releaseBuild/azureDevOps/releaseBuild.yml
@@ -207,7 +207,7 @@ stages:
pool:
name: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
steps:
- checkout: self
clean: true
diff --git a/tools/releaseBuild/azureDevOps/releasePipeline.yml b/tools/releaseBuild/azureDevOps/releasePipeline.yml
index 7915ab537b9..49cc3536c20 100644
--- a/tools/releaseBuild/azureDevOps/releasePipeline.yml
+++ b/tools/releaseBuild/azureDevOps/releasePipeline.yml
@@ -179,7 +179,7 @@ stages:
pool: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
variables:
- group: 'Staging_ACR'
@@ -195,7 +195,7 @@ stages:
pool: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
variables:
- group: 'Azure Blob variable group'
@@ -231,7 +231,7 @@ stages:
pool: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
steps:
- template: templates/release-ReleaseToNuGet.yml
@@ -240,7 +240,7 @@ stages:
pool: PowerShell1ES
demands:
- - ImageOverride -equals MMSUbuntu20.04TLS
+ - ImageOverride -equals PSMMSUbuntu20.04-Secure
variables:
- group: 'AzDevOpsArtifacts'
@@ -337,7 +337,7 @@ stages:
pool: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
variables:
- group: 'Azure Blob variable group'
diff --git a/tools/releaseBuild/azureDevOps/templates/checkAzureContainer.yml b/tools/releaseBuild/azureDevOps/templates/checkAzureContainer.yml
index f9a2ee2dc64..49082849e9b 100644
--- a/tools/releaseBuild/azureDevOps/templates/checkAzureContainer.yml
+++ b/tools/releaseBuild/azureDevOps/templates/checkAzureContainer.yml
@@ -10,7 +10,7 @@ jobs:
pool:
name: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
steps:
- checkout: self
clean: true
diff --git a/tools/releaseBuild/azureDevOps/templates/compliance.yml b/tools/releaseBuild/azureDevOps/templates/compliance.yml
index 9eebd8be888..0c4a162bb11 100644
--- a/tools/releaseBuild/azureDevOps/templates/compliance.yml
+++ b/tools/releaseBuild/azureDevOps/templates/compliance.yml
@@ -18,7 +18,7 @@ jobs:
pool:
name: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
# APIScan can take a long time
timeoutInMinutes: 180
diff --git a/tools/releaseBuild/azureDevOps/templates/compliance/compliance.yml b/tools/releaseBuild/azureDevOps/templates/compliance/compliance.yml
index 92d5a7daec4..e81bdcf3709 100644
--- a/tools/releaseBuild/azureDevOps/templates/compliance/compliance.yml
+++ b/tools/releaseBuild/azureDevOps/templates/compliance/compliance.yml
@@ -19,7 +19,7 @@ jobs:
pool:
name: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
# APIScan can take a long time
timeoutInMinutes: 180
diff --git a/tools/releaseBuild/azureDevOps/templates/compliance/generateNotice.yml b/tools/releaseBuild/azureDevOps/templates/compliance/generateNotice.yml
index d904531d6c7..0e63dd9007b 100644
--- a/tools/releaseBuild/azureDevOps/templates/compliance/generateNotice.yml
+++ b/tools/releaseBuild/azureDevOps/templates/compliance/generateNotice.yml
@@ -16,7 +16,7 @@ jobs:
pool:
name: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
timeoutInMinutes: 15
diff --git a/tools/releaseBuild/azureDevOps/templates/json.yml b/tools/releaseBuild/azureDevOps/templates/json.yml
index cbea46d9eea..714978022a5 100644
--- a/tools/releaseBuild/azureDevOps/templates/json.yml
+++ b/tools/releaseBuild/azureDevOps/templates/json.yml
@@ -15,7 +15,7 @@ jobs:
pool:
name: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
steps:
#- task: @
diff --git a/tools/releaseBuild/azureDevOps/templates/linux.yml b/tools/releaseBuild/azureDevOps/templates/linux.yml
index dc415c36c78..3d9625201a0 100644
--- a/tools/releaseBuild/azureDevOps/templates/linux.yml
+++ b/tools/releaseBuild/azureDevOps/templates/linux.yml
@@ -10,7 +10,7 @@ jobs:
pool:
name: PowerShell1ES
demands:
- - ImageOverride -equals MMSUbuntu20.04TLS
+ - ImageOverride -equals PSMMSUbuntu20.04-Secure
dependsOn: ${{ parameters.parentJob }}
variables:
- name: runCodesignValidationInjection
@@ -151,7 +151,7 @@ jobs:
pool:
name: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
variables:
- name: buildName
value: ${{ parameters.buildName }}
diff --git a/tools/releaseBuild/azureDevOps/templates/mac-file-signing.yml b/tools/releaseBuild/azureDevOps/templates/mac-file-signing.yml
index 648708fa4be..a5c5a0811ec 100644
--- a/tools/releaseBuild/azureDevOps/templates/mac-file-signing.yml
+++ b/tools/releaseBuild/azureDevOps/templates/mac-file-signing.yml
@@ -9,7 +9,7 @@ jobs:
pool:
name: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
variables:
- group: ESRP
diff --git a/tools/releaseBuild/azureDevOps/templates/mac-package-signing.yml b/tools/releaseBuild/azureDevOps/templates/mac-package-signing.yml
index b1cf9917424..e41d0609d4c 100644
--- a/tools/releaseBuild/azureDevOps/templates/mac-package-signing.yml
+++ b/tools/releaseBuild/azureDevOps/templates/mac-package-signing.yml
@@ -9,7 +9,7 @@ jobs:
pool:
name: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
variables:
- group: ESRP
- name: runCodesignValidationInjection
diff --git a/tools/releaseBuild/azureDevOps/templates/nuget.yml b/tools/releaseBuild/azureDevOps/templates/nuget.yml
index b868c9dbdf7..ac4b181b7b8 100644
--- a/tools/releaseBuild/azureDevOps/templates/nuget.yml
+++ b/tools/releaseBuild/azureDevOps/templates/nuget.yml
@@ -10,7 +10,7 @@ jobs:
pool:
name: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
timeoutInMinutes: 90
diff --git a/tools/releaseBuild/azureDevOps/templates/release-MsixBundle.yml b/tools/releaseBuild/azureDevOps/templates/release-MsixBundle.yml
index 1b424568994..607fda3ad40 100644
--- a/tools/releaseBuild/azureDevOps/templates/release-MsixBundle.yml
+++ b/tools/releaseBuild/azureDevOps/templates/release-MsixBundle.yml
@@ -4,7 +4,7 @@ jobs:
pool: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
variables:
- group: msixTools
diff --git a/tools/releaseBuild/azureDevOps/templates/release-UpdateDepsJson.yml b/tools/releaseBuild/azureDevOps/templates/release-UpdateDepsJson.yml
index f0c43c4b93f..bd465fedd27 100644
--- a/tools/releaseBuild/azureDevOps/templates/release-UpdateDepsJson.yml
+++ b/tools/releaseBuild/azureDevOps/templates/release-UpdateDepsJson.yml
@@ -4,7 +4,7 @@ jobs:
pool: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
variables:
- group: 'Azure Blob variable group'
diff --git a/tools/releaseBuild/azureDevOps/templates/vpackReleaseJob.yml b/tools/releaseBuild/azureDevOps/templates/vpackReleaseJob.yml
index de6252bc6e3..10bc19044fb 100644
--- a/tools/releaseBuild/azureDevOps/templates/vpackReleaseJob.yml
+++ b/tools/releaseBuild/azureDevOps/templates/vpackReleaseJob.yml
@@ -10,7 +10,7 @@ jobs:
condition: succeeded()
pool: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
steps:
- checkout: self
clean: true
diff --git a/tools/releaseBuild/azureDevOps/templates/windows-component-governance.yml b/tools/releaseBuild/azureDevOps/templates/windows-component-governance.yml
index 5719b4f272c..53947655d90 100644
--- a/tools/releaseBuild/azureDevOps/templates/windows-component-governance.yml
+++ b/tools/releaseBuild/azureDevOps/templates/windows-component-governance.yml
@@ -12,7 +12,7 @@ jobs:
pool:
name: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
steps:
- checkout: self
diff --git a/tools/releaseBuild/azureDevOps/templates/windows-hosted-build.yml b/tools/releaseBuild/azureDevOps/templates/windows-hosted-build.yml
index 5d5484b14d8..16554ae7d41 100644
--- a/tools/releaseBuild/azureDevOps/templates/windows-hosted-build.yml
+++ b/tools/releaseBuild/azureDevOps/templates/windows-hosted-build.yml
@@ -16,7 +16,7 @@ jobs:
pool:
name: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
variables:
- name: runCodesignValidationInjection
value: false
diff --git a/tools/releaseBuild/azureDevOps/templates/windows-package-signing.yml b/tools/releaseBuild/azureDevOps/templates/windows-package-signing.yml
index 8a45af1bc05..66bf7af7b3d 100644
--- a/tools/releaseBuild/azureDevOps/templates/windows-package-signing.yml
+++ b/tools/releaseBuild/azureDevOps/templates/windows-package-signing.yml
@@ -10,7 +10,7 @@ jobs:
pool:
name: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
variables:
- name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE
value: 1
diff --git a/tools/releaseBuild/azureDevOps/templates/windows-packaging.yml b/tools/releaseBuild/azureDevOps/templates/windows-packaging.yml
index 6b0d28b96f2..a93908a8f6c 100644
--- a/tools/releaseBuild/azureDevOps/templates/windows-packaging.yml
+++ b/tools/releaseBuild/azureDevOps/templates/windows-packaging.yml
@@ -16,7 +16,7 @@ jobs:
pool:
name: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
variables:
- name: BuildConfiguration
value: ${{ parameters.BuildConfiguration }}
diff --git a/tools/releaseBuild/azureDevOps/vpackRelease.yml b/tools/releaseBuild/azureDevOps/vpackRelease.yml
index 03482479cbb..875e2d25c3c 100644
--- a/tools/releaseBuild/azureDevOps/vpackRelease.yml
+++ b/tools/releaseBuild/azureDevOps/vpackRelease.yml
@@ -27,7 +27,7 @@ stages:
pool: PowerShell1ES
demands:
- - ImageOverride -equals MMS2019TLS
+ - ImageOverride -equals PSMMS2019-Secure
steps:
- checkout: self
From 9f722ef95072fddd975b1b7b584bf11b2e857a69 Mon Sep 17 00:00:00 2001
From: Ilya
Date: Thu, 27 Jan 2022 21:53:52 +0500
Subject: [PATCH 025/209] Use more efficient platform detection API (#16760)
* Use more efficient platform detection API
Use static and const new OperatingSystem class API to avoid string comparisons and extra allocations.
* Remove unused variable
---
.../commands/management/ComputerUnix.cs | 2 +-
.../commands/utility/UnblockFile.cs | 2 +-
src/Microsoft.PowerShell.GlobalTool.Shim/GlobalToolShim.cs | 2 +-
.../CoreCLR/CorePsAssemblyLoadContext.cs | 6 +++---
src/System.Management.Automation/CoreCLR/CorePsPlatform.cs | 6 +++---
.../namespaces/FileSystemProvider.cs | 7 ++-----
.../security/SecuritySupport.cs | 2 +-
src/powershell/Program.cs | 3 ++-
test/perf/benchmarks/Engine.ScriptBlock.cs | 4 ++--
9 files changed, 16 insertions(+), 18 deletions(-)
diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs
index fb01823790f..a11fb0270c9 100644
--- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs
+++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs
@@ -67,7 +67,7 @@ public sealed class StopComputerCommand : CommandLineCmdletBase
protected override void BeginProcessing()
{
var args = "-P now";
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ if (Platform.IsMacOS)
{
args = "now";
}
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs
index 5c297ed86d5..6633a66f96f 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs
@@ -137,7 +137,7 @@ protected override void ProcessRecord()
}
}
#else
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ if (Platform.IsLinux)
{
string errorMessage = UnblockFileStrings.LinuxNotSupported;
Exception e = new PlatformNotSupportedException(errorMessage);
diff --git a/src/Microsoft.PowerShell.GlobalTool.Shim/GlobalToolShim.cs b/src/Microsoft.PowerShell.GlobalTool.Shim/GlobalToolShim.cs
index 3b253582b63..33675e30b44 100644
--- a/src/Microsoft.PowerShell.GlobalTool.Shim/GlobalToolShim.cs
+++ b/src/Microsoft.PowerShell.GlobalTool.Shim/GlobalToolShim.cs
@@ -26,7 +26,7 @@ public static class EntryPoint
public static int Main(string[] args)
{
var currentPath = new FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location).Directory.FullName;
- var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ var isWindows = Platform.IsWindows;
string platformFolder = isWindows ? WinFolderName : UnixFolderName;
diff --git a/src/System.Management.Automation/CoreCLR/CorePsAssemblyLoadContext.cs b/src/System.Management.Automation/CoreCLR/CorePsAssemblyLoadContext.cs
index 43e6ad17eb4..fad311df98d 100644
--- a/src/System.Management.Automation/CoreCLR/CorePsAssemblyLoadContext.cs
+++ b/src/System.Management.Automation/CoreCLR/CorePsAssemblyLoadContext.cs
@@ -540,17 +540,17 @@ private static string GetNativeDllSubFolderName(out string ext)
ext = string.Empty;
var processArch = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant();
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ if (Platform.IsWindows)
{
folderName = "win-" + processArch;
ext = ".dll";
}
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ else if (Platform.IsLinux)
{
folderName = "linux-" + processArch;
ext = ".so";
}
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ else if (Platform.IsMacOS)
{
folderName = "osx-x64";
ext = ".dylib";
diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
index 45be8721b7f..1986c4c63be 100644
--- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
+++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
@@ -22,7 +22,7 @@ public static bool IsLinux
{
get
{
- return RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+ return OperatingSystem.IsLinux();
}
}
@@ -33,7 +33,7 @@ public static bool IsMacOS
{
get
{
- return RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
+ return OperatingSystem.IsMacOS();
}
}
@@ -44,7 +44,7 @@ public static bool IsWindows
{
get
{
- return RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ return OperatingSystem.IsWindows();
}
}
diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs
index c57ed5ed2dc..15d742e40bc 100644
--- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs
+++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs
@@ -469,9 +469,7 @@ protected override ProviderInfo Start(ProviderInfo providerInfo)
#if !UNIX
// The placeholder mode management APIs Rtl(Set|Query)(Process|Thread)PlaceholderCompatibilityMode
// are only supported starting with Windows 10 version 1803 (build 17134)
- Version minBuildForPlaceHolderAPIs = new Version(10, 0, 17134, 0);
-
- if (Environment.OSVersion.Version >= minBuildForPlaceHolderAPIs)
+ if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134, 0))
{
// let's be safe, don't change the PlaceHolderCompatibilityMode if the current one is not what we expect
if (NativeMethods.RtlQueryProcessPlaceholderCompatibilityMode() == NativeMethods.PHCM_DISGUISE_PLACEHOLDER)
@@ -2724,8 +2722,7 @@ private static bool WinCreateSymbolicLink(string path, string strTargetPath, boo
// The new AllowUnprivilegedCreate is only available on Win10 build 14972 or newer
var flags = isDirectory ? NativeMethods.SymbolicLinkFlags.Directory : NativeMethods.SymbolicLinkFlags.File;
- Version minBuildOfDeveloperMode = new Version(10, 0, 14972, 0);
- if (Environment.OSVersion.Version >= minBuildOfDeveloperMode)
+ if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 14972, 0))
{
flags |= NativeMethods.SymbolicLinkFlags.AllowUnprivilegedCreate;
}
diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs
index 7b5087411ec..6321d301ced 100644
--- a/src/System.Management.Automation/security/SecuritySupport.cs
+++ b/src/System.Management.Automation/security/SecuritySupport.cs
@@ -1213,7 +1213,7 @@ private void ResolveFromStoreById(ResolutionPurpose purpose, out ErrorRecord err
storeCU.Open(OpenFlags.ReadOnly);
X509Certificate2Collection storeCerts = storeCU.Certificates;
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ if (Platform.IsWindows)
{
using (var storeLM = new X509Store("my", StoreLocation.LocalMachine))
{
diff --git a/src/powershell/Program.cs b/src/powershell/Program.cs
index 3fee683618f..d085db22b98 100644
--- a/src/powershell/Program.cs
+++ b/src/powershell/Program.cs
@@ -5,6 +5,7 @@
using System;
using System.IO;
+using System.Management.Automation;
using System.Reflection;
using System.Runtime.InteropServices;
@@ -89,7 +90,7 @@ private static void AttemptExecPwshLogin(string[] args)
return;
}
- bool isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+ bool isLinux = Platform.IsLinux;
// The first byte (ASCII char) of the name of this process, used to detect '-' for login
byte procNameFirstByte;
diff --git a/test/perf/benchmarks/Engine.ScriptBlock.cs b/test/perf/benchmarks/Engine.ScriptBlock.cs
index ad373dd5f99..1f9ac989428 100644
--- a/test/perf/benchmarks/Engine.ScriptBlock.cs
+++ b/test/perf/benchmarks/Engine.ScriptBlock.cs
@@ -36,7 +36,7 @@ public IEnumerable ValuesForScript()
yield return @"[System.IO.Path]::HasExtension('')";
// Test on COM method invocation.
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ if (Platform.IsWindows)
{
yield return @"$sh=New-Object -ComObject Shell.Application; $sh.Namespace('c:\')";
yield return @"$fs=New-Object -ComObject scripting.filesystemobject; $fs.Drives";
@@ -54,7 +54,7 @@ public void GlobalSetup()
// believe that there is no need to run many ops in each interation. However, the subsequent runs
// of this method is much faster than the first run, and this causes 'MinIterationTime' warnings
// to our benchmarks and make the benchmark results not reliable.
- // Calling this method once in 'GlobalSetup' is a workaround.
+ // Calling this method once in 'GlobalSetup' is a workaround.
// See https://github.com/dotnet/BenchmarkDotNet/issues/837#issuecomment-828600157
scriptBlock.Invoke();
}
From 8c532529693dbccb46fb4edf9d69b0667f524630 Mon Sep 17 00:00:00 2001
From: "Joel Sallow (/u/ta11ow)" <32407840+vexx32@users.noreply.github.com>
Date: Thu, 27 Jan 2022 12:55:54 -0500
Subject: [PATCH 026/209] Add support to allow invoking method with generic
type arguments (#12412)
---
.../CommandCompletion/CompletionAnalysis.cs | 8 +
.../engine/CoreAdapter.cs | 73 +++++--
.../engine/MshMemberInfo.cs | 60 ++++--
.../engine/parser/Compiler.cs | 66 ++++--
.../engine/parser/Parser.cs | 147 ++++++++++---
.../engine/parser/ast.cs | 177 +++++++++++++++-
.../resources/ExtendedTypeSystem.resx | 6 +
.../TabCompletion/TabCompletion.Tests.ps1 | 16 ++
.../Parser/MethodInvocation.Tests.ps1 | 193 ++++++++++++++++++
9 files changed, 653 insertions(+), 93 deletions(-)
diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs
index 18bd6cbed3d..081fbd07a74 100644
--- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs
+++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs
@@ -2107,6 +2107,14 @@ private List GetResultForIdentifier(CompletionContext completi
result = CompletionCompleters.CompleteCommandArgument(completionContext);
replacementIndex = completionContext.ReplacementIndex;
replacementLength = completionContext.ReplacementLength;
+
+ if (result.Count == 0
+ && completionContext.TokenAtCursor.TokenFlags.HasFlag(TokenFlags.TypeName)
+ && lastAst?.Find(a => a is MemberExpressionAst, searchNestedScriptBlocks: false) is not null)
+ {
+ result = CompletionCompleters.CompleteType(completionContext.TokenAtCursor.Text).ToList();
+ }
+
return result;
}
diff --git a/src/System.Management.Automation/engine/CoreAdapter.cs b/src/System.Management.Automation/engine/CoreAdapter.cs
index 1a623e92f1e..b9e8979852a 100644
--- a/src/System.Management.Automation/engine/CoreAdapter.cs
+++ b/src/System.Management.Automation/engine/CoreAdapter.cs
@@ -1394,32 +1394,37 @@ private static MethodInformation FindBestMethodImpl(
// be turned into an array.
// We also skip the optimization if the number of arguments and parameters is different
// so we let the loop deal with possible optional parameters.
- if ((methods.Length == 1) &&
- (!methods[0].hasVarArgs) &&
- (!methods[0].isGeneric) &&
- (methods[0].method == null || !(methods[0].method.DeclaringType.IsGenericTypeDefinition)) &&
+ if (methods.Length == 1
+ && !methods[0].hasVarArgs
// generic methods need to be double checked in a loop below - generic methods can be rejected if type inference fails
- (methods[0].parameters.Length == arguments.Length))
+ && !methods[0].isGeneric
+ && (methods[0].method is null || !methods[0].method.DeclaringType.IsGenericTypeDefinition)
+ && methods[0].parameters.Length == arguments.Length)
{
return methods[0];
}
Type[] argumentTypes = arguments.Select(EffectiveArgumentType).ToArray();
+ Type[] genericParameters = invocationConstraints?.GenericTypeParameters ?? Array.Empty();
List candidates = new List();
for (int i = 0; i < methods.Length; i++)
{
- MethodInformation method = methods[i];
+ MethodInformation methodInfo = methods[i];
- if (method.method != null && method.method.DeclaringType.IsGenericTypeDefinition)
+ if (methodInfo.method?.DeclaringType.IsGenericTypeDefinition == true
+ || (!methodInfo.isGeneric && genericParameters.Length > 0))
{
- continue; // skip methods defined by an *open* generic type
+ // If method is defined by an *open* generic type, or
+ // if generic parameters were provided and this method isn't generic, skip it.
+ continue;
}
- if (method.isGeneric)
+ if (methodInfo.isGeneric)
{
Type[] argumentTypesForTypeInference = new Type[argumentTypes.Length];
Array.Copy(argumentTypes, argumentTypesForTypeInference, argumentTypes.Length);
- if (invocationConstraints != null && invocationConstraints.ParameterTypes != null)
+
+ if (invocationConstraints?.ParameterTypes is not null)
{
int parameterIndex = 0;
foreach (Type typeConstraintFromCallSite in invocationConstraints.ParameterTypes)
@@ -1433,20 +1438,36 @@ private static MethodInformation FindBestMethodImpl(
}
}
- method = TypeInference.Infer(method, argumentTypesForTypeInference);
- if (method == null)
+ if (genericParameters.Length > 0 && methodInfo.method is MethodInfo originalMethod)
+ {
+ try
+ {
+ methodInfo = new MethodInformation(
+ originalMethod.MakeGenericMethod(genericParameters),
+ parametersToIgnore: 0);
+ }
+ catch (ArgumentException)
+ {
+ // Just skip this possibility if the generic type parameters can't be used to make
+ // a valid generic method here.
+ continue;
+ }
+ }
+
+ methodInfo = TypeInference.Infer(methodInfo, argumentTypesForTypeInference);
+ if (methodInfo is null)
{
// Skip generic methods for which we cannot infer type arguments
continue;
}
}
- if (!IsInvocationTargetConstraintSatisfied(method, invocationConstraints))
+ if (!IsInvocationTargetConstraintSatisfied(methodInfo, invocationConstraints))
{
continue;
}
- ParameterInformation[] parameters = method.parameters;
+ ParameterInformation[] parameters = methodInfo.parameters;
if (arguments.Length != parameters.Length)
{
// Skip methods w/ an incorrect # of arguments.
@@ -1454,7 +1475,7 @@ private static MethodInformation FindBestMethodImpl(
if (arguments.Length > parameters.Length)
{
// If too many args,it's only OK if the method is varargs.
- if (!method.hasVarArgs)
+ if (!methodInfo.hasVarArgs)
{
continue;
}
@@ -1462,12 +1483,12 @@ private static MethodInformation FindBestMethodImpl(
else
{
// Too few args, OK if there are optionals, or varargs with the param array omitted
- if (!method.hasOptional && (!method.hasVarArgs || (arguments.Length + 1) != parameters.Length))
+ if (!methodInfo.hasOptional && (!methodInfo.hasVarArgs || (arguments.Length + 1) != parameters.Length))
{
continue;
}
- if (method.hasOptional)
+ if (methodInfo.hasOptional)
{
// Count optionals. This code is rarely hit, mainly when calling code in the
// assembly Microsoft.VisualBasic. If it were more frequent, the optional count
@@ -1490,7 +1511,7 @@ private static MethodInformation FindBestMethodImpl(
}
}
- OverloadCandidate candidate = new OverloadCandidate(method, arguments.Length);
+ OverloadCandidate candidate = new OverloadCandidate(methodInfo, arguments.Length);
for (int j = 0; candidate != null && j < parameters.Length; j++)
{
ParameterInformation parameter = parameters[j];
@@ -1590,6 +1611,22 @@ private static MethodInformation FindBestMethodImpl(
methods[0].method.DeclaringType.FullName);
return null;
}
+ else if (genericParameters.Length != 0 && genericParameters.Contains(null))
+ {
+ errorId = "TypeNotFoundForGenericMethod";
+ errorMsg = ExtendedTypeSystem.MethodGenericArgumentTypeNotFoundException;
+ return null;
+ }
+ else if (genericParameters.Length != 0)
+ {
+ errorId = "MethodCountCouldNotFindBestGeneric";
+ errorMsg = string.Format(
+ ExtendedTypeSystem.MethodGenericArgumentCountException,
+ methods[0].method.Name,
+ genericParameters.Length,
+ arguments.Length);
+ return null;
+ }
else
{
errorId = "MethodCountCouldNotFindBest";
diff --git a/src/System.Management.Automation/engine/MshMemberInfo.cs b/src/System.Management.Automation/engine/MshMemberInfo.cs
index 73ee243f6e8..ef6d185a606 100644
--- a/src/System.Management.Automation/engine/MshMemberInfo.cs
+++ b/src/System.Management.Automation/engine/MshMemberInfo.cs
@@ -1909,9 +1909,18 @@ internal class PSMethodInvocationConstraints
internal PSMethodInvocationConstraints(
Type methodTargetType,
Type[] parameterTypes)
+ : this(methodTargetType, genericTypeParameters: null, parameterTypes)
{
- this.MethodTargetType = methodTargetType;
+ }
+
+ internal PSMethodInvocationConstraints(
+ Type methodTargetType,
+ Type[] genericTypeParameters,
+ Type[] parameterTypes)
+ {
+ MethodTargetType = methodTargetType;
_parameterTypes = parameterTypes;
+ GenericTypeParameters = genericTypeParameters;
}
///
@@ -1926,6 +1935,11 @@ internal PSMethodInvocationConstraints(
private readonly Type[] _parameterTypes;
+ ///
+ /// Gets the generic type parameters for the method invocation.
+ ///
+ public Type[] GenericTypeParameters { get; }
+
internal static bool EqualsForCollection(ICollection xs, ICollection ys)
{
if (xs == null)
@@ -1946,8 +1960,6 @@ internal static bool EqualsForCollection(ICollection xs, ICollection ys
return xs.SequenceEqual(ys);
}
- // TODO: IEnumerable genericTypeParameters { get; private set; }
-
public bool Equals(PSMethodInvocationConstraints other)
{
if (other is null)
@@ -1970,6 +1982,11 @@ public bool Equals(PSMethodInvocationConstraints other)
return false;
}
+ if (!EqualsForCollection(GenericTypeParameters, other.GenericTypeParameters))
+ {
+ return false;
+ }
+
return true;
}
@@ -1994,18 +2011,7 @@ public override bool Equals(object obj)
}
public override int GetHashCode()
- {
- // algorithm based on https://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode
- unchecked
- {
- int result = 61;
-
- result = result * 397 + (MethodTargetType != null ? MethodTargetType.GetHashCode() : 0);
- result = result * 397 + ParameterTypes.SequenceGetHashCode();
-
- return result;
- }
- }
+ => HashCode.Combine(MethodTargetType, ParameterTypes, GenericTypeParameters);
public override string ToString()
{
@@ -2018,6 +2024,22 @@ public override string ToString()
separator = " ";
}
+ if (GenericTypeParameters != null)
+ {
+ sb.Append(separator);
+ sb.Append("genericTypeParams: ");
+
+ separator = string.Empty;
+ foreach (Type parameter in GenericTypeParameters)
+ {
+ sb.Append(separator);
+ sb.Append(ToStringCodeMethods.Type(parameter, dropNamespaces: true));
+ separator = ", ";
+ }
+
+ separator = " ";
+ }
+
if (_parameterTypes != null)
{
sb.Append(separator);
@@ -5035,7 +5057,7 @@ internal struct Enumerator : IEnumerator
private readonly PSMemberInfoInternalCollection _allMembers;
///
- /// Constructs this instance to enumerate over members.
+ /// Initializes a new instance of the class to enumerate over members.
///
/// Members we are enumerating.
internal Enumerator(PSMemberInfoIntegratingCollection integratingCollection)
@@ -5063,8 +5085,8 @@ internal Enumerator(PSMemberInfoIntegratingCollection integratingCollection)
/// Moves to the next element in the enumeration.
///
///
- /// false if there are no more elements to enumerate
- /// true otherwise
+ /// If there are no more elements to enumerate, returns false.
+ /// Returns true otherwise.
///
public bool MoveNext()
{
@@ -5093,7 +5115,7 @@ public bool MoveNext()
}
///
- /// Current PSMemberInfo in the enumeration.
+ /// Gets the current PSMemberInfo in the enumeration.
///
/// For invalid arguments.
T IEnumerator.Current
diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs
index b8ca57b4b05..5f7c54d00c2 100644
--- a/src/System.Management.Automation/engine/parser/Compiler.cs
+++ b/src/System.Management.Automation/engine/parser/Compiler.cs
@@ -1174,24 +1174,34 @@ internal static Type GetTypeConstraintForMethodResolution(ExpressionAst expr)
return firstConvert?.Type.TypeName.GetReflectionType();
}
- internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution(Type targetType, Type argType)
+ internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution(
+ Type targetType,
+ Type argType,
+ Type[] genericArguments = null)
{
- if (targetType == null && argType == null)
+ if (targetType is null
+ && argType is null
+ && (genericArguments is null || genericArguments.Length == 0))
{
return null;
}
- return new PSMethodInvocationConstraints(targetType, new[] { argType });
+ return new PSMethodInvocationConstraints(targetType, genericArguments, new[] { argType });
}
- internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution(Type targetType, Type[] argTypes)
+ internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution(
+ Type targetType,
+ Type[] argTypes,
+ Type[] genericArguments = null)
{
- if (targetType == null && (argTypes == null || argTypes.Length == 0))
+ if (targetType is null
+ && (argTypes is null || argTypes.Length == 0)
+ && (genericArguments is null || genericArguments.Length == 0))
{
return null;
}
- return new PSMethodInvocationConstraints(targetType, argTypes);
+ return new PSMethodInvocationConstraints(targetType, genericArguments, argTypes);
}
internal static Expression ConvertValue(TypeConstraintAst typeConstraint, Expression expr)
@@ -6338,17 +6348,47 @@ public object VisitMemberExpression(MemberExpressionAst memberExpressionAst)
internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(InvokeMemberExpressionAst invokeMemberExpressionAst)
{
- var arguments = invokeMemberExpressionAst.Arguments;
+ ReadOnlyCollection arguments = invokeMemberExpressionAst.Arguments;
+ Type[] argumentTypes = null;
+ if (arguments is not null)
+ {
+ argumentTypes = new Type[arguments.Count];
+ for (var i = 0; i < arguments.Count; i++)
+ {
+ argumentTypes[i] = GetTypeConstraintForMethodResolution(arguments[i]);
+ }
+ }
+
var targetTypeConstraint = GetTypeConstraintForMethodResolution(invokeMemberExpressionAst.Expression);
- return CombineTypeConstraintForMethodResolution(
- targetTypeConstraint,
- arguments?.Select(Compiler.GetTypeConstraintForMethodResolution).ToArray());
+
+ ReadOnlyCollection genericArguments = invokeMemberExpressionAst.GenericTypeArguments;
+ Type[] genericTypeArguments = null;
+ if (genericArguments is not null)
+ {
+ genericTypeArguments = new Type[genericArguments.Count];
+ for (var i = 0; i < genericArguments.Count; i++)
+ {
+ genericTypeArguments[i] = genericArguments[i].GetReflectionType();
+ }
+ }
+
+ return CombineTypeConstraintForMethodResolution(targetTypeConstraint, argumentTypes, genericTypeArguments);
}
internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(BaseCtorInvokeMemberExpressionAst invokeMemberExpressionAst)
{
Type targetTypeConstraint = null;
- var arguments = invokeMemberExpressionAst.Arguments;
+ ReadOnlyCollection arguments = invokeMemberExpressionAst.Arguments;
+ Type[] argumentTypes = null;
+ if (arguments is not null)
+ {
+ argumentTypes = new Type[arguments.Count];
+ for (var i = 0; i < arguments.Count; i++)
+ {
+ argumentTypes[i] = GetTypeConstraintForMethodResolution(arguments[i]);
+ }
+ }
+
TypeDefinitionAst typeDefinitionAst = Ast.GetAncestorTypeDefinitionAst(invokeMemberExpressionAst);
if (typeDefinitionAst != null)
{
@@ -6359,9 +6399,7 @@ internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(BaseCto
Diagnostics.Assert(false, "BaseCtorInvokeMemberExpressionAst must be used only inside TypeDefinitionAst");
}
- return CombineTypeConstraintForMethodResolution(
- targetTypeConstraint,
- arguments?.Select(Compiler.GetTypeConstraintForMethodResolution).ToArray());
+ return CombineTypeConstraintForMethodResolution(targetTypeConstraint, argumentTypes, genericArguments: null);
}
internal Expression InvokeMember(
diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs
index e3d08ca38ab..a855be0b0a7 100644
--- a/src/System.Management.Automation/engine/parser/Parser.cs
+++ b/src/System.Management.Automation/engine/parser/Parser.cs
@@ -1358,7 +1358,7 @@ private ITypeName FinishTypeNameRule(Token typeName, bool unBracketedGenericArg
case TokenKind.LBracket:
case TokenKind.Identifier:
- return GenericTypeArgumentsRule(typeName, token, unBracketedGenericArg);
+ return GenericTypeNameRule(typeName, token, unBracketedGenericArg);
default:
// ErrorRecovery: sync to ']', and return non-null to avoid cascading errors.
@@ -1438,7 +1438,7 @@ private ITypeName GetSingleGenericArgument(Token firstToken)
return typeName;
}
- private ITypeName GenericTypeArgumentsRule(Token genericTypeName, Token firstToken, bool unBracketedGenericArg)
+ private List GenericTypeArgumentsRule(Token firstToken, out Token lastToken)
{
Diagnostics.Assert(firstToken.Kind == TokenKind.Identifier || firstToken.Kind == TokenKind.LBracket, "unexpected first token");
RuntimeHelpers.EnsureSufficientExecutionStack();
@@ -1447,20 +1447,18 @@ private ITypeName GenericTypeArgumentsRule(Token genericTypeName, Token firstTok
ITypeName typeName = GetSingleGenericArgument(firstToken);
genericArguments.Add(typeName);
- Token commaOrRBracketToken;
- Token token;
while (true)
{
SkipNewlines();
- commaOrRBracketToken = NextToken();
- if (commaOrRBracketToken.Kind != TokenKind.Comma)
+ lastToken = NextToken();
+ if (lastToken.Kind != TokenKind.Comma)
{
break;
}
SkipNewlines();
- token = PeekToken();
+ Token token = PeekToken();
if (token.Kind == TokenKind.Identifier || token.Kind == TokenKind.LBracket)
{
SkipToken();
@@ -1468,43 +1466,55 @@ private ITypeName GenericTypeArgumentsRule(Token genericTypeName, Token firstTok
}
else
{
- ReportIncompleteInput(After(commaOrRBracketToken),
+ ReportIncompleteInput(
+ After(lastToken),
nameof(ParserStrings.MissingTypename),
ParserStrings.MissingTypename);
- typeName = new TypeName(commaOrRBracketToken.Extent, ":ErrorTypeName:");
+ typeName = new TypeName(lastToken.Extent, ":ErrorTypeName:");
}
genericArguments.Add(typeName);
}
- if (commaOrRBracketToken.Kind != TokenKind.RBracket)
+ return genericArguments;
+ }
+
+ private ITypeName GenericTypeNameRule(Token genericTypeName, Token firstToken, bool unbracketedGenericArg)
+ {
+ List genericArguments = GenericTypeArgumentsRule(firstToken, out Token rBracketToken);
+
+ if (rBracketToken.Kind != TokenKind.RBracket)
{
// ErrorRecovery: pretend we had the closing bracket and just continue on.
-
- UngetToken(commaOrRBracketToken);
- ReportIncompleteInput(Before(commaOrRBracketToken),
+ UngetToken(rBracketToken);
+ ReportIncompleteInput(
+ Before(rBracketToken),
nameof(ParserStrings.EndSquareBracketExpectedAtEndOfAttribute),
ParserStrings.EndSquareBracketExpectedAtEndOfAttribute);
- commaOrRBracketToken = null;
+ rBracketToken = null;
}
var openGenericType = new TypeName(genericTypeName.Extent, genericTypeName.Text);
- var result = new GenericTypeName(ExtentOf(genericTypeName.Extent, ExtentFromFirstOf(commaOrRBracketToken, genericArguments.LastOrDefault(), firstToken)),
- openGenericType, genericArguments);
- token = PeekToken();
+ var result = new GenericTypeName(
+ ExtentOf(genericTypeName.Extent, ExtentFromFirstOf(rBracketToken, genericArguments.LastOrDefault(), firstToken)),
+ openGenericType,
+ genericArguments);
+
+ Token token = PeekToken();
if (token.Kind == TokenKind.LBracket)
{
SkipToken();
return CompleteArrayTypeName(result, openGenericType, NextToken());
}
- if (token.Kind == TokenKind.Comma && !unBracketedGenericArg)
+ if (token.Kind == TokenKind.Comma && !unbracketedGenericArg)
{
SkipToken();
string assemblyNameSpec = _tokenizer.GetAssemblyNameSpec();
if (string.IsNullOrEmpty(assemblyNameSpec))
{
- ReportError(After(token),
+ ReportError(
+ After(token),
nameof(ParserStrings.MissingAssemblyNameSpecification),
ParserStrings.MissingAssemblyNameSpecification);
}
@@ -7735,6 +7745,7 @@ private ExpressionAst MemberAccessRule(ExpressionAst targetExpr, Token operatorT
// On entry, we've verified that operatorToken is not preceded by whitespace.
CommandElementAst member = MemberNameRule();
+ List genericTypeArguments = null;
if (member == null)
{
@@ -7747,34 +7758,106 @@ private ExpressionAst MemberAccessRule(ExpressionAst targetExpr, Token operatorT
member = GetSingleCommandArgument(CommandArgumentContext.CommandArgument) ??
new ErrorExpressionAst(ExtentOf(targetExpr, operatorToken));
}
- else
+ else if (_ungotToken == null)
{
+ // Member name may be an incomplete token like `$a.$(Command-Name`; we do not look for generic args or
+ // invocation token(s) if the member name token is recognisably incomplete.
+ genericTypeArguments = GenericMethodArgumentsRule(out Token rBracket);
Token lParen = NextInvokeMemberToken();
+
if (lParen != null)
{
+ // When we reach here, we either had a legit section of generic arguments (in which case, `rBracket`
+ // won't be null), or we saw `lParen` directly following the member token (in which case, `rBracket`
+ // will be null).
+ int endColumnNumber = rBracket is null ? member.Extent.EndColumnNumber : rBracket.Extent.EndColumnNumber;
+
Diagnostics.Assert(lParen.Kind == TokenKind.LParen || lParen.Kind == TokenKind.LCurly, "token kind incorrect");
- Diagnostics.Assert(member.Extent.EndColumnNumber == lParen.Extent.StartColumnNumber,
- "member and paren must be adjacent");
- return MemberInvokeRule(targetExpr, lParen, operatorToken, member);
+ Diagnostics.Assert(
+ endColumnNumber == lParen.Extent.StartColumnNumber,
+ "member and paren must be adjacent when the method is not generic");
+ return MemberInvokeRule(targetExpr, lParen, operatorToken, member, genericTypeArguments);
}
}
return new MemberExpressionAst(
- ExtentOf(targetExpr, member),
- targetExpr,
- member,
- @static: operatorToken.Kind == TokenKind.ColonColon,
- nullConditional: operatorToken.Kind == TokenKind.QuestionDot);
+ ExtentOf(targetExpr, member),
+ targetExpr,
+ member,
+ @static: operatorToken.Kind == TokenKind.ColonColon,
+ nullConditional: operatorToken.Kind == TokenKind.QuestionDot,
+ genericTypeArguments);
+ }
+
+ private List GenericMethodArgumentsRule(out Token rBracketToken)
+ {
+ List genericTypes = null;
+
+ int resyncIndex = _tokenizer.GetRestorePoint();
+ Token lBracket = NextToken();
+ rBracketToken = null;
+
+ if (lBracket.Kind != TokenKind.LBracket)
+ {
+ // We cannot avoid this Resync(); if we use PeekToken() to try to avoid a Resync(), the method called
+ // after this [`NextInvokeMemberToken()` or `NextMemberAccessToken()`] will note that an _ungotToken
+ // is present and assume an error state. That will cause any property accesses or non-generic method
+ // calls to throw a parse error.
+ Resync(resyncIndex);
+ return null;
+ }
+
+ // This is either a InvokeMember expression with generic type arguments, or some sort of collection index
+ // on a property.
+ TokenizerMode oldTokenizerMode = _tokenizer.Mode;
+ try
+ {
+ // Switch to typename mode to avoid aggressive argument tokenization.
+ SetTokenizerMode(TokenizerMode.TypeName);
+
+ SkipNewlines();
+ Token firstToken = NextToken();
+ if (firstToken.Kind == TokenKind.Identifier || firstToken.Kind == TokenKind.LBracket)
+ {
+ resyncIndex = -1;
+ genericTypes = GenericTypeArgumentsRule(firstToken, out rBracketToken);
+
+ if (rBracketToken.Kind != TokenKind.RBracket)
+ {
+ UngetToken(rBracketToken);
+ ReportIncompleteInput(
+ Before(rBracketToken),
+ nameof(ParserStrings.EndSquareBracketExpectedAtEndOfType),
+ ParserStrings.EndSquareBracketExpectedAtEndOfType);
+ rBracketToken = null;
+ }
+ }
+ }
+ finally
+ {
+ SetTokenizerMode(oldTokenizerMode);
+ }
+
+ if (resyncIndex > 0)
+ {
+ Resync(resyncIndex);
+ }
+
+ return genericTypes;
}
- private ExpressionAst MemberInvokeRule(ExpressionAst targetExpr, Token lBracket, Token operatorToken, CommandElementAst member)
+ private ExpressionAst MemberInvokeRule(
+ ExpressionAst targetExpr,
+ Token lBracket,
+ Token operatorToken,
+ CommandElementAst member,
+ IList genericTypes)
{
// G invocation-expression: target-expression passed as a parameter. lBracket can be '(' or '{'.
// G target-expression member-name invoke-param-list
// G invoke-param-list:
// G '(' invoke-param-paren-list
// G script-block
-
IScriptExtent lastExtent = null;
List arguments;
@@ -7785,6 +7868,7 @@ private ExpressionAst MemberInvokeRule(ExpressionAst targetExpr, Token lBracket,
else
{
arguments = new List();
+
// handle the construct $x.methodName{2+2} as through it had been written $x.methodName({2+2})
SkipNewlines();
ExpressionAst argument = ScriptBlockExpressionRule(lBracket);
@@ -7798,7 +7882,8 @@ private ExpressionAst MemberInvokeRule(ExpressionAst targetExpr, Token lBracket,
member,
arguments,
operatorToken.Kind == TokenKind.ColonColon,
- operatorToken.Kind == TokenKind.QuestionDot);
+ operatorToken.Kind == TokenKind.QuestionDot,
+ genericTypes);
}
private List InvokeParamParenListRule(Token lParen, out IScriptExtent lastExtent)
diff --git a/src/System.Management.Automation/engine/parser/ast.cs b/src/System.Management.Automation/engine/parser/ast.cs
index b40a8e66a1e..8671f3c58f2 100644
--- a/src/System.Management.Automation/engine/parser/ast.cs
+++ b/src/System.Management.Automation/engine/parser/ast.cs
@@ -8001,7 +8001,7 @@ internal bool IsRef()
public class MemberExpressionAst : ExpressionAst, ISupportsAssignment
{
///
- /// Construct an ast to reference a property.
+ /// Initializes a new instance of the class.
///
///
/// The extent of the expression, starting with the expression before the operator '.' or '::' and ending after
@@ -8012,10 +8012,16 @@ public class MemberExpressionAst : ExpressionAst, ISupportsAssignment
/// True if the '::' operator was used, false if '.' is used.
/// True if the member access is for a static member, using '::', false if accessing a member on an instance using '.'.
///
+ /// The generic type arguments passed to the member.
///
/// If , , or is null.
///
- public MemberExpressionAst(IScriptExtent extent, ExpressionAst expression, CommandElementAst member, bool @static)
+ public MemberExpressionAst(
+ IScriptExtent extent,
+ ExpressionAst expression,
+ CommandElementAst member,
+ bool @static,
+ IList genericTypes)
: base(extent)
{
if (expression == null || member == null)
@@ -8028,6 +8034,35 @@ public MemberExpressionAst(IScriptExtent extent, ExpressionAst expression, Comma
this.Member = member;
SetParent(member);
this.Static = @static;
+
+ if (genericTypes is not null && genericTypes.Count > 0)
+ {
+ this.GenericTypeArguments = new ReadOnlyCollection(genericTypes);
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The extent of the expression, starting with the expression before the operator '.' or '::' and ending after
+ /// membername or expression naming the member.
+ ///
+ /// The expression before the member access operator '.' or '::'.
+ /// The name or expression naming the member to access.
+ /// True if the '::' operator was used, false if '.' is used.
+ /// True if the member access is for a static member, using '::', false if accessing a member on an instance using '.'.
+ ///
+ ///
+ /// If , , or is null.
+ ///
+ public MemberExpressionAst(
+ IScriptExtent extent,
+ ExpressionAst expression,
+ CommandElementAst member,
+ bool @static)
+ : this(extent, expression, member, @static, genericTypes: null)
+ {
}
///
@@ -8041,15 +8076,46 @@ public MemberExpressionAst(IScriptExtent extent, ExpressionAst expression, Comma
/// The name or expression naming the member to access.
/// True if the '::' operator was used, false if '.' or '?.' is used.
/// True if '?.' used.
+ /// The generic type arguments passed to the member.
///
/// If , , or is null.
///
- public MemberExpressionAst(IScriptExtent extent, ExpressionAst expression, CommandElementAst member, bool @static, bool nullConditional)
- : this(extent, expression, member, @static)
+ public MemberExpressionAst(
+ IScriptExtent extent,
+ ExpressionAst expression,
+ CommandElementAst member,
+ bool @static,
+ bool nullConditional,
+ IList genericTypes)
+ : this(extent, expression, member, @static, genericTypes)
{
this.NullConditional = nullConditional;
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The extent of the expression, starting with the expression before the operator '.', '::' or '?.' and ending after
+ /// membername or expression naming the member.
+ ///
+ /// The expression before the member access operator '.', '::' or '?.'.
+ /// The name or expression naming the member to access.
+ /// True if the '::' operator was used, false if '.' or '?.' is used.
+ /// True if '?.' used.
+ ///
+ /// If , , or is null.
+ ///
+ public MemberExpressionAst(
+ IScriptExtent extent,
+ ExpressionAst expression,
+ CommandElementAst member,
+ bool @static,
+ bool nullConditional)
+ : this(extent, expression, member, @static, nullConditional, genericTypes: null)
+ {
+ }
+
///
/// The expression that produces the value to retrieve the member from. This property is never null.
///
@@ -8070,6 +8136,11 @@ public MemberExpressionAst(IScriptExtent extent, ExpressionAst expression, Comma
///
public bool NullConditional { get; protected set; }
+ ///
+ /// Gets a list of generic type arguments passed to this member.
+ ///
+ public ReadOnlyCollection GenericTypeArguments { get; }
+
///
/// Copy the MemberExpressionAst instance.
///
@@ -8077,7 +8148,14 @@ public override Ast Copy()
{
var newExpression = CopyElement(this.Expression);
var newMember = CopyElement(this.Member);
- return new MemberExpressionAst(this.Extent, newExpression, newMember, this.Static, this.NullConditional);
+
+ return new MemberExpressionAst(
+ this.Extent,
+ newExpression,
+ newMember,
+ this.Static,
+ this.NullConditional,
+ this.GenericTypeArguments);
}
#region Visitors
@@ -8113,7 +8191,7 @@ IAssignableValue ISupportsAssignment.GetAssignableValue()
public class InvokeMemberExpressionAst : MemberExpressionAst, ISupportsAssignment
{
///
- /// Construct an instance of a method invocation expression.
+ /// Initializes a new instance of the class.
///
///
/// The extent of the expression, starting with the expression before the invocation operator and ending with the
@@ -8125,11 +8203,18 @@ public class InvokeMemberExpressionAst : MemberExpressionAst, ISupportsAssignmen
///
/// True if the invocation is for a static method, using '::', false if invoking a method on an instance using '.'.
///
+ /// The generic type arguments passed to the method.
///
/// If is null.
///
- public InvokeMemberExpressionAst(IScriptExtent extent, ExpressionAst expression, CommandElementAst method, IEnumerable arguments, bool @static)
- : base(extent, expression, method, @static)
+ public InvokeMemberExpressionAst(
+ IScriptExtent extent,
+ ExpressionAst expression,
+ CommandElementAst method,
+ IEnumerable arguments,
+ bool @static,
+ IList genericTypes)
+ : base(extent, expression, method, @static, genericTypes)
{
if (arguments != null && arguments.Any())
{
@@ -8138,6 +8223,32 @@ public InvokeMemberExpressionAst(IScriptExtent extent, ExpressionAst expression,
}
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The extent of the expression, starting with the expression before the invocation operator and ending with the
+ /// closing paren after the arguments.
+ ///
+ /// The expression before the invocation operator ('.', '::').
+ /// The method to invoke.
+ /// The arguments to pass to the method.
+ ///
+ /// True if the invocation is for a static method, using '::', false if invoking a method on an instance using '.'.
+ ///
+ ///
+ /// If is null.
+ ///
+ public InvokeMemberExpressionAst(
+ IScriptExtent extent,
+ ExpressionAst expression,
+ CommandElementAst method,
+ IEnumerable arguments,
+ bool @static)
+ : this(extent, expression, method, arguments, @static, genericTypes: null)
+ {
+ }
+
///
/// Initializes a new instance of the class.
///
@@ -8152,15 +8263,51 @@ public InvokeMemberExpressionAst(IScriptExtent extent, ExpressionAst expression,
/// True if the invocation is for a static method, using '::', false if invoking a method on an instance using '.' or '?.'.
///
/// True if the operator used is '?.'.
+ /// The generic type arguments passed to the method.
///
/// If is null.
///
- public InvokeMemberExpressionAst(IScriptExtent extent, ExpressionAst expression, CommandElementAst method, IEnumerable arguments, bool @static, bool nullConditional)
- : this(extent, expression, method, arguments, @static)
+ public InvokeMemberExpressionAst(
+ IScriptExtent extent,
+ ExpressionAst expression,
+ CommandElementAst method,
+ IEnumerable arguments,
+ bool @static,
+ bool nullConditional,
+ IList genericTypes)
+ : this(extent, expression, method, arguments, @static, genericTypes)
{
this.NullConditional = nullConditional;
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The extent of the expression, starting with the expression before the invocation operator and ending with the
+ /// closing paren after the arguments.
+ ///
+ /// The expression before the invocation operator ('.', '::' or '?.').
+ /// The method to invoke.
+ /// The arguments to pass to the method.
+ ///
+ /// True if the invocation is for a static method, using '::', false if invoking a method on an instance using '.' or '?.'.
+ ///
+ /// True if the operator used is '?.'.
+ ///
+ /// If is null.
+ ///
+ public InvokeMemberExpressionAst(
+ IScriptExtent extent,
+ ExpressionAst expression,
+ CommandElementAst method,
+ IEnumerable arguments,
+ bool @static,
+ bool nullConditional)
+ : this(extent, expression, method, arguments, @static, nullConditional, genericTypes: null)
+ {
+ }
+
///
/// The non-empty collection of arguments to pass when invoking the method, or null if no arguments were specified.
///
@@ -8174,7 +8321,15 @@ public override Ast Copy()
var newExpression = CopyElement(this.Expression);
var newMethod = CopyElement(this.Member);
var newArguments = CopyElements(this.Arguments);
- return new InvokeMemberExpressionAst(this.Extent, newExpression, newMethod, newArguments, this.Static, this.NullConditional);
+
+ return new InvokeMemberExpressionAst(
+ this.Extent,
+ newExpression,
+ newMethod,
+ newArguments,
+ this.Static,
+ this.NullConditional,
+ this.GenericTypeArguments);
}
#region Visitors
diff --git a/src/System.Management.Automation/resources/ExtendedTypeSystem.resx b/src/System.Management.Automation/resources/ExtendedTypeSystem.resx
index 6a697cba888..d617cbf7a38 100644
--- a/src/System.Management.Automation/resources/ExtendedTypeSystem.resx
+++ b/src/System.Management.Automation/resources/ExtendedTypeSystem.resx
@@ -156,6 +156,12 @@
Cannot find an overload for "{0}" and the argument count: "{1}".
+
+ Could not find a suitable generic method overload for "{0}" with "{1}" type parameters, and the argument count: "{2}".
+
+
+ One or more of the generic type parameters provided for the method "{0}" refers to a type which cannot be found.
+
Multiple ambiguous overloads found for "{0}" and the argument count: "{1}".
diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1
index 6513bd87253..b1e4b68a9b9 100644
--- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1
+++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1
@@ -43,6 +43,22 @@ Describe "TabCompletion" -Tags CI {
$res.CompletionMatches[0].CompletionText | Should -BeExactly 'CompareTo('
}
+ It 'should complete generic type parameters for static methods' {
+ $script = '[array]::Empty[pscu'
+
+ $results = TabExpansion2 -inputScript $script -cursorColumn $script.Length
+ $results.CompletionMatches.CompletionText | Should -Contain 'pscustomobject'
+ }
+
+ It 'should complete generic type parameters for instance methods' {
+ $script = '
+ $dict = [System.Collections.Concurrent.ConcurrentDictionary[string, int]]::new()
+ $dict.AddOrUpdate[pscu'
+
+ $results = TabExpansion2 -inputScript $script -cursorColumn $script.Length
+ $results.CompletionMatches.CompletionText | Should -Contain 'pscustomobject'
+ }
+
It 'Should complete Magic foreach' {
$res = TabExpansion2 -inputScript '(1..10).Fo' -cursorColumn '(1..10).Fo'.Length
$res.CompletionMatches[0].CompletionText | Should -BeExactly 'ForEach('
diff --git a/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 b/test/powershell/Language/Parser/MethodInvocation.Tests.ps1
index f28b6801389..e6a91891174 100644
--- a/test/powershell/Language/Parser/MethodInvocation.Tests.ps1
+++ b/test/powershell/Language/Parser/MethodInvocation.Tests.ps1
@@ -81,3 +81,196 @@ namespace MSFT_716893
([MSFT_716893.IInterface1]$proxy).BaseOperation(22) | Should -Be "3 - 22"
}
}
+
+Describe 'Generic Method invocation' -Tags 'CI' {
+
+ BeforeAll {
+ $EmptyArrayCases = @(
+ @{
+ Script = '[Array]::Empty[string]()'
+ ExpectedType = [string[]]
+ }
+ @{
+ Script = '[Array]::Empty[System.Collections.Generic.Dictionary[System.Numerics.BigInteger, System.Collections.Generic.List[string[,]]]]()'
+ ExpectedType = [System.Collections.Generic.Dictionary[System.Numerics.BigInteger, System.Collections.Generic.List[string[, ]]][]]
+ }
+ @{
+ Script = '[Array]::$("Empty")[[System.Collections.Generic.Dictionary[[System.String, System.Private.CoreLib],[System.Numerics.BigInteger, System.Runtime.Numerics]], System.Private.CoreLib]]()'
+ ExpectedType = [System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib], [System.Numerics.BigInteger, System.Runtime.Numerics]][], System.Private.CoreLib]
+ }
+ )
+
+ $ExpectedParseErrors = @(
+ @{
+ Script = '$object.Method[incompl'
+ ExpectedErrors = 'EndSquareBracketExpectedAtEndOfType'
+ ErrorCount = 1
+ }
+ @{
+ Script = '[type]::Member[incompl'
+ ExpectedErrors = 'EndSquareBracketExpectedAtEndOfType'
+ ErrorCount = 1
+ }
+ @{
+ Script = '$object.Method[Type1[Type2'
+ ExpectedErrors = 'UnexpectedToken','EndSquareBracketExpectedAtEndOfType'
+ ErrorCount = 2
+ }
+ @{
+ Script = '[array]::empty[type]]()'
+ ExpectedErrors = 'UnexpectedToken', 'ExpectedExpression'
+ ErrorCount = 2
+ }
+ @{
+ Script = '$object.Method[type,]()'
+ ExpectedErrors = 'MissingTypeName'
+ ErrorCount = 1
+ }
+ @{
+ Script = '$object.Method[]()'
+ ExpectedErrors = 'MissingArrayIndexExpression', 'UnexpectedToken', 'ExpectedExpression'
+ ErrorCount = 3
+ }
+ @{
+ Script = '$object.Method[,]()'
+ ExpectedErrors = 'MissingExpressionAfterOperator', 'UnexpectedToken', 'ExprectedExpression'
+ ErrorCount = 3
+ }
+ @{
+ Script = '$object.Method[,type]()'
+ ExpectedErrors = 'MissingExpressionAfterOperator', 'UnexpectedToken', 'ExpectedExpression'
+ ErrorCount = 3
+ }
+ @{
+ Script = '$object.Method[type()'
+ ExpectedErrors = 'UnexpectedToken', 'ExpectedExpression'
+ ErrorCount = 2
+ }
+ @{
+ Script = '$object.Method[type)'
+ ExpectedErrors = 'EndSquareBracketExpectedAtEndOfType', 'UnexpectedToken'
+ ErrorCount = 2
+ }
+ )
+ }
+
+ It 'does not throw a parse error for "