From 6f5774f03175b881c2a887c6ea884f53aa9956a6 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sat, 24 Aug 2019 00:48:08 -0400 Subject: [PATCH 01/59] :recycle: Rename parameter set constants --- .../management/TestConnectionCommand.cs | 104 +++++++++--------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 19974c8891b..8d55985661e 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -16,57 +16,57 @@ namespace Microsoft.PowerShell.Commands /// /// The implementation of the "Test-Connection" cmdlet. /// - [Cmdlet(VerbsDiagnostic.Test, "Connection", DefaultParameterSetName = ParameterSetPingCount, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135266")] - [OutputType(typeof(PingReport), ParameterSetName = new string[] { ParameterSetPingCount })] - [OutputType(typeof(PingReply), ParameterSetName = new string[] { ParameterSetPingContinues, ParameterSetDetectionOfMTUSize })] - [OutputType(typeof(bool), ParameterSetName = new string[] { ParameterSetPingCount, ParameterSetPingContinues, ParameterSetConnectionByTCPPort })] - [OutputType(typeof(Int32), ParameterSetName = new string[] { ParameterSetDetectionOfMTUSize })] - [OutputType(typeof(TraceRouteReply), ParameterSetName = new string[] { ParameterSetTraceRoute })] + [Cmdlet(VerbsDiagnostic.Test, "Connection", DefaultParameterSetName = DefaultPingSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135266")] + [OutputType(typeof(PingReport), ParameterSetName = new string[] { DefaultPingSet })] + [OutputType(typeof(PingReply), ParameterSetName = new string[] { RepeatPingSet, MtuSizeDetectSet })] + [OutputType(typeof(bool), ParameterSetName = new string[] { DefaultPingSet, RepeatPingSet, TcpPortSet })] + [OutputType(typeof(Int32), ParameterSetName = new string[] { MtuSizeDetectSet })] + [OutputType(typeof(TraceRouteReply), ParameterSetName = new string[] { TraceRouteSet })] public class TestConnectionCommand : PSCmdlet { - private const string ParameterSetPingCount = "PingCount"; - private const string ParameterSetPingContinues = "PingContinues"; - private const string ParameterSetTraceRoute = "TraceRoute"; - private const string ParameterSetConnectionByTCPPort = "ConnectionByTCPPort"; - private const string ParameterSetDetectionOfMTUSize = "DetectionOfMTUSize"; + private const string DefaultPingSet = "PingCount"; + private const string RepeatPingSet = "PingContinues"; + private const string TraceRouteSet = "TraceRoute"; + private const string TcpPortSet = "ConnectionByTCPPort"; + private const string MtuSizeDetectSet = "DetectionOfMTUSize"; #region Parameters /// /// Do ping test. /// - [Parameter(ParameterSetName = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] + [Parameter(ParameterSetName = DefaultPingSet)] + [Parameter(ParameterSetName = RepeatPingSet)] public SwitchParameter Ping { get; set; } = true; /// /// Force using IPv4 protocol. /// - [Parameter(ParameterSetName = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] - [Parameter(ParameterSetName = ParameterSetTraceRoute)] - [Parameter(ParameterSetName = ParameterSetDetectionOfMTUSize)] - [Parameter(ParameterSetName = ParameterSetConnectionByTCPPort)] + [Parameter(ParameterSetName = DefaultPingSet)] + [Parameter(ParameterSetName = RepeatPingSet)] + [Parameter(ParameterSetName = TraceRouteSet)] + [Parameter(ParameterSetName = MtuSizeDetectSet)] + [Parameter(ParameterSetName = TcpPortSet)] public SwitchParameter IPv4 { get; set; } /// /// Force using IPv6 protocol. /// - [Parameter(ParameterSetName = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] - [Parameter(ParameterSetName = ParameterSetTraceRoute)] - [Parameter(ParameterSetName = ParameterSetDetectionOfMTUSize)] - [Parameter(ParameterSetName = ParameterSetConnectionByTCPPort)] + [Parameter(ParameterSetName = DefaultPingSet)] + [Parameter(ParameterSetName = RepeatPingSet)] + [Parameter(ParameterSetName = TraceRouteSet)] + [Parameter(ParameterSetName = MtuSizeDetectSet)] + [Parameter(ParameterSetName = TcpPortSet)] public SwitchParameter IPv6 { get; set; } /// /// Do reverse DNS lookup to get names for IP addresses. /// - [Parameter(ParameterSetName = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] - [Parameter(ParameterSetName = ParameterSetTraceRoute)] - [Parameter(ParameterSetName = ParameterSetDetectionOfMTUSize)] - [Parameter(ParameterSetName = ParameterSetConnectionByTCPPort)] + [Parameter(ParameterSetName = DefaultPingSet)] + [Parameter(ParameterSetName = RepeatPingSet)] + [Parameter(ParameterSetName = TraceRouteSet)] + [Parameter(ParameterSetName = MtuSizeDetectSet)] + [Parameter(ParameterSetName = TcpPortSet)] public SwitchParameter ResolveDestination { get; set; } /// @@ -74,10 +74,10 @@ public class TestConnectionCommand : PSCmdlet /// The default is Local Host. /// Remoting is not yet implemented internally in the cmdlet. /// - [Parameter(ParameterSetName = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] - [Parameter(ParameterSetName = ParameterSetTraceRoute)] - [Parameter(ParameterSetName = ParameterSetConnectionByTCPPort)] + [Parameter(ParameterSetName = DefaultPingSet)] + [Parameter(ParameterSetName = RepeatPingSet)] + [Parameter(ParameterSetName = TraceRouteSet)] + [Parameter(ParameterSetName = TcpPortSet)] public string Source { get; } = Dns.GetHostName(); /// @@ -86,9 +86,9 @@ public class TestConnectionCommand : PSCmdlet /// they decrement the Time-to-Live (TTL) value found in the packet header. /// The default (from Windows) is 128 hops. /// - [Parameter(ParameterSetName = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] - [Parameter(ParameterSetName = ParameterSetTraceRoute)] + [Parameter(ParameterSetName = DefaultPingSet)] + [Parameter(ParameterSetName = RepeatPingSet)] + [Parameter(ParameterSetName = TraceRouteSet)] [ValidateRange(0, sMaxHops)] [Alias("Ttl", "TimeToLive", "Hops")] public int MaxHops { get; set; } = sMaxHops; @@ -99,7 +99,7 @@ public class TestConnectionCommand : PSCmdlet /// Count of attempts. /// The default (from Windows) is 4 times. /// - [Parameter(ParameterSetName = ParameterSetPingCount)] + [Parameter(ParameterSetName = DefaultPingSet)] [ValidateRange(ValidateRangeKind.Positive)] public int Count { get; set; } = 4; @@ -107,8 +107,8 @@ public class TestConnectionCommand : PSCmdlet /// Delay between attempts. /// The default (from Windows) is 1 second. /// - [Parameter(ParameterSetName = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] + [Parameter(ParameterSetName = DefaultPingSet)] + [Parameter(ParameterSetName = RepeatPingSet)] [ValidateRange(ValidateRangeKind.Positive)] public int Delay { get; set; } = 1; @@ -117,8 +117,8 @@ public class TestConnectionCommand : PSCmdlet /// The default (from Windows) is 32 bites. /// Max value is 65500 (limit from Windows API). /// - [Parameter(ParameterSetName = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] + [Parameter(ParameterSetName = DefaultPingSet)] + [Parameter(ParameterSetName = RepeatPingSet)] [Alias("Size", "Bytes", "BS")] [ValidateRange(0, 65500)] public int BufferSize { get; set; } = DefaultSendBufferSize; @@ -127,15 +127,15 @@ public class TestConnectionCommand : PSCmdlet /// Don't fragment ICMP packages. /// Currently CoreFX not supports this on Unix. /// - [Parameter(ParameterSetName = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] + [Parameter(ParameterSetName = DefaultPingSet)] + [Parameter(ParameterSetName = RepeatPingSet)] public SwitchParameter DontFragment { get; set; } /// /// Continue ping until user press Ctrl-C /// or Int.MaxValue threshold reached. /// - [Parameter(ParameterSetName = ParameterSetPingContinues)] + [Parameter(ParameterSetName = RepeatPingSet)] public SwitchParameter Continues { get; set; } /// @@ -169,20 +169,20 @@ public class TestConnectionCommand : PSCmdlet /// /// Detect MTU size. /// - [Parameter(Mandatory = true, ParameterSetName = ParameterSetDetectionOfMTUSize)] + [Parameter(Mandatory = true, ParameterSetName = MtuSizeDetectSet)] public SwitchParameter MTUSizeDetect { get; set; } /// /// Do traceroute test. /// - [Parameter(Mandatory = true, ParameterSetName = ParameterSetTraceRoute)] + [Parameter(Mandatory = true, ParameterSetName = TraceRouteSet)] public SwitchParameter Traceroute { get; set; } /// /// Do tcp connection test. /// [ValidateRange(0, 65535)] - [Parameter(Mandatory = true, ParameterSetName = ParameterSetConnectionByTCPPort)] + [Parameter(Mandatory = true, ParameterSetName = TcpPortSet)] public int TCPPort { get; set; } #endregion Parameters @@ -196,7 +196,7 @@ protected override void BeginProcessing() switch (ParameterSetName) { - case ParameterSetPingContinues: + case RepeatPingSet: Count = int.MaxValue; break; } @@ -211,17 +211,17 @@ protected override void ProcessRecord() { switch (ParameterSetName) { - case ParameterSetPingCount: - case ParameterSetPingContinues: + case DefaultPingSet: + case RepeatPingSet: ProcessPing(targetName); break; - case ParameterSetDetectionOfMTUSize: + case MtuSizeDetectSet: ProcessMTUSize(targetName); break; - case ParameterSetTraceRoute: + case TraceRouteSet: ProcessTraceroute(targetName); break; - case ParameterSetConnectionByTCPPort: + case TcpPortSet: ProcessConnectionByTCPPort(targetName); break; } From 897ec6eea5822b568895d45730d696f46282f202 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sat, 24 Aug 2019 00:49:35 -0400 Subject: [PATCH 02/59] :memo: Rename parameter sets --- .../commands/management/TestConnectionCommand.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 8d55985661e..a0169ca9280 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -24,11 +24,11 @@ namespace Microsoft.PowerShell.Commands [OutputType(typeof(TraceRouteReply), ParameterSetName = new string[] { TraceRouteSet })] public class TestConnectionCommand : PSCmdlet { - private const string DefaultPingSet = "PingCount"; - private const string RepeatPingSet = "PingContinues"; + private const string DefaultPingSet = "DefaultPing"; + private const string RepeatPingSet = "RepeatPing"; private const string TraceRouteSet = "TraceRoute"; - private const string TcpPortSet = "ConnectionByTCPPort"; - private const string MtuSizeDetectSet = "DetectionOfMTUSize"; + private const string TcpPortSet = "TcpPort"; + private const string MtuSizeDetectSet = "MtuSizeDetect"; #region Parameters From f02c95aedf6e9fa7493dc1d3eff13c2cd58032a5 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sat, 24 Aug 2019 00:59:39 -0400 Subject: [PATCH 03/59] :art: Whitespace style fixes --- .../management/TestConnectionCommand.cs | 174 +++++++++++------- 1 file changed, 105 insertions(+), 69 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index a0169ca9280..400ae502e82 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -158,10 +158,11 @@ public class TestConnectionCommand : PSCmdlet /// /// Destination - computer name or IP address. /// - [Parameter(Mandatory = true, - Position = 0, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] + [Parameter( + Mandatory = true, + Position = 0, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] [Alias("ComputerName")] public string[] TargetName { get; set; } @@ -292,10 +293,11 @@ private void WriteConnectionTestHeader(string resolvedTargetName, string targetA private void WriteConnectionTestProgress(string targetNameOrAddress, string targetAddress, int timeout) { - var msg = StringUtil.Format(TestConnectionResources.ConnectionTestDescription, - targetNameOrAddress, - targetAddress, - timeout); + var msg = StringUtil.Format( + TestConnectionResources.ConnectionTestDescription, + targetNameOrAddress, + targetAddress, + timeout); ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, msg); WriteProgress(record); } @@ -350,14 +352,16 @@ private void ProcessTraceroute(String targetNameOrAddress) } catch (PingException ex) { - string message = StringUtil.Format(TestConnectionResources.NoPingResult, - resolvedTargetName, - ex.Message); + string message = StringUtil.Format( + TestConnectionResources.NoPingResult, + resolvedTargetName, + ex.Message); Exception pingException = new System.Net.NetworkInformation.PingException(message, ex.InnerException); - ErrorRecord errorRecord = new ErrorRecord(pingException, - TestConnectionExceptionId, - ErrorCategory.ResourceUnavailable, - resolvedTargetName); + ErrorRecord errorRecord = new ErrorRecord( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + resolvedTargetName); WriteError(errorRecord); continue; @@ -381,7 +385,9 @@ private void ProcessTraceroute(String targetNameOrAddress) WriteTraceRouteProgress(traceRouteReply); traceRouteResult.Replies.Add(traceRouteReply); - } while (reply != null && currentHop <= sMaxHops && (reply.Status == IPStatus.TtlExpired || reply.Status == IPStatus.TimedOut)); + } while (reply != null + && currentHop <= sMaxHops + && (reply.Status == IPStatus.TtlExpired || reply.Status == IPStatus.TimedOut)); WriteTraceRouteFooter(); @@ -397,7 +403,11 @@ private void ProcessTraceroute(String targetNameOrAddress) private void WriteConsoleTraceRouteHeader(string resolvedTargetName, string targetAddress) { - _testConnectionProgressBarActivity = StringUtil.Format(TestConnectionResources.TraceRouteStart, resolvedTargetName, targetAddress, MaxHops); + _testConnectionProgressBarActivity = StringUtil.Format( + TestConnectionResources.TraceRouteStart, + resolvedTargetName, + targetAddress, + MaxHops); WriteInformation(_testConnectionProgressBarActivity, s_PSHostTag); @@ -412,15 +422,25 @@ private void WriteTraceRouteProgress(TraceRouteReply traceRouteReply) { string msg = string.Empty; - if (traceRouteReply.PingReplies[2].Status == IPStatus.TtlExpired || traceRouteReply.PingReplies[2].Status == IPStatus.Success) + if (traceRouteReply.PingReplies[2].Status == IPStatus.TtlExpired + || traceRouteReply.PingReplies[2].Status == IPStatus.Success) { var routerAddress = traceRouteReply.ReplyRouterAddress.ToString(); var routerName = traceRouteReply.ReplyRouterName ?? routerAddress; - var roundtripTime0 = traceRouteReply.PingReplies[0].Status == IPStatus.TimedOut ? "*" : traceRouteReply.PingReplies[0].RoundtripTime.ToString(); - var roundtripTime1 = traceRouteReply.PingReplies[1].Status == IPStatus.TimedOut ? "*" : traceRouteReply.PingReplies[1].RoundtripTime.ToString(); - msg = StringUtil.Format(TestConnectionResources.TraceRouteReply, - traceRouteReply.Hop, roundtripTime0, roundtripTime1, traceRouteReply.PingReplies[2].RoundtripTime.ToString(), - routerName, routerAddress); + var roundtripTime0 = traceRouteReply.PingReplies[0].Status == IPStatus.TimedOut + ? "*" + : traceRouteReply.PingReplies[0].RoundtripTime.ToString(); + var roundtripTime1 = traceRouteReply.PingReplies[1].Status == IPStatus.TimedOut + ? "*" + : traceRouteReply.PingReplies[1].RoundtripTime.ToString(); + msg = StringUtil.Format( + TestConnectionResources.TraceRouteReply, + traceRouteReply.Hop, + roundtripTime0, + roundtripTime1, + traceRouteReply.PingReplies[2].RoundtripTime.ToString(), + routerName, + routerAddress); } else { @@ -541,7 +561,11 @@ private void ProcessMTUSize(String targetNameOrAddress) WriteMTUSizeProgress(CurrentMTUSize, retry); - WriteDebug(StringUtil.Format("LowMTUSize: {0}, CurrentMTUSize: {1}, HighMTUSize: {2}", LowMTUSize, CurrentMTUSize, HighMTUSize)); + WriteDebug(StringUtil.Format( + "LowMTUSize: {0}, CurrentMTUSize: {1}, HighMTUSize: {2}", + LowMTUSize, + CurrentMTUSize, + HighMTUSize)); reply = sender.Send(targetAddress, timeout, buffer, pingOptions); @@ -562,14 +586,16 @@ private void ProcessMTUSize(String targetNameOrAddress) // Target host don't reply - try again up to 'Count'. if (retry >= Count) { - string message = StringUtil.Format(TestConnectionResources.NoPingResult, - targetAddress, - reply.Status.ToString()); + string message = StringUtil.Format( + TestConnectionResources.NoPingResult, + targetAddress, + reply.Status.ToString()); Exception pingException = new System.Net.NetworkInformation.PingException(message); - ErrorRecord errorRecord = new ErrorRecord(pingException, - TestConnectionExceptionId, - ErrorCategory.ResourceUnavailable, - targetAddress); + ErrorRecord errorRecord = new ErrorRecord( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + targetAddress); WriteError(errorRecord); return; } @@ -590,10 +616,11 @@ private void ProcessMTUSize(String targetNameOrAddress) { string message = StringUtil.Format(TestConnectionResources.NoPingResult, targetAddress, ex.Message); Exception pingException = new System.Net.NetworkInformation.PingException(message, ex.InnerException); - ErrorRecord errorRecord = new ErrorRecord(pingException, - TestConnectionExceptionId, - ErrorCategory.ResourceUnavailable, - targetAddress); + ErrorRecord errorRecord = new ErrorRecord( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + targetAddress); WriteError(errorRecord); return; } @@ -622,10 +649,11 @@ private void ProcessMTUSize(String targetNameOrAddress) private void WriteMTUSizeHeader(string resolvedTargetName, string targetAddress) { - _testConnectionProgressBarActivity = StringUtil.Format(TestConnectionResources.MTUSizeDetectStart, - resolvedTargetName, - targetAddress, - BufferSize); + _testConnectionProgressBarActivity = StringUtil.Format( + TestConnectionResources.MTUSizeDetectStart, + resolvedTargetName, + targetAddress, + BufferSize); ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace); WriteProgress(record); @@ -685,10 +713,11 @@ private void ProcessPing(String targetNameOrAddress) { string message = StringUtil.Format(TestConnectionResources.NoPingResult, resolvedTargetName, ex.Message); Exception pingException = new System.Net.NetworkInformation.PingException(message, ex.InnerException); - ErrorRecord errorRecord = new ErrorRecord(pingException, - TestConnectionExceptionId, - ErrorCategory.ResourceUnavailable, - resolvedTargetName); + ErrorRecord errorRecord = new ErrorRecord( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + resolvedTargetName); WriteError(errorRecord); quietResult = false; @@ -738,16 +767,18 @@ private void ProcessPing(String targetNameOrAddress) private void WritePingHeader(string resolvedTargetName, string targetAddress) { - _testConnectionProgressBarActivity = StringUtil.Format(TestConnectionResources.MTUSizeDetectStart, - resolvedTargetName, - targetAddress, - BufferSize); + _testConnectionProgressBarActivity = StringUtil.Format( + TestConnectionResources.MTUSizeDetectStart, + resolvedTargetName, + targetAddress, + BufferSize); WriteInformation(_testConnectionProgressBarActivity, s_PSHostTag); - ProgressRecord record = new ProgressRecord(s_ProgressId, - _testConnectionProgressBarActivity, - ProgressRecordSpace); + ProgressRecord record = new ProgressRecord( + s_ProgressId, + _testConnectionProgressBarActivity, + ProgressRecordSpace); WriteProgress(record); } @@ -760,11 +791,12 @@ private void WritePingProgress(PingReply reply) } else { - msg = StringUtil.Format(TestConnectionResources.PingReply, - reply.Address.ToString(), - reply.Buffer.Length, - reply.RoundtripTime, - reply.Options?.Ttl); + msg = StringUtil.Format( + TestConnectionResources.PingReply, + reply.Address.ToString(), + reply.Buffer.Length, + reply.RoundtripTime, + reply.Options?.Ttl); } WriteInformation(msg, s_PSHostTag); @@ -840,14 +872,16 @@ private bool InitProcessPing(String targetNameOrAddress, out string resolvedTarg } catch (Exception ex) { - string message = StringUtil.Format(TestConnectionResources.NoPingResult, - resolvedTargetName, - TestConnectionResources.CannotResolveTargetName); + string message = StringUtil.Format( + TestConnectionResources.NoPingResult, + resolvedTargetName, + TestConnectionResources.CannotResolveTargetName); Exception pingException = new System.Net.NetworkInformation.PingException(message, ex); - ErrorRecord errorRecord = new ErrorRecord(pingException, - TestConnectionExceptionId, - ErrorCategory.ResourceUnavailable, - resolvedTargetName); + ErrorRecord errorRecord = new ErrorRecord( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + resolvedTargetName); WriteError(errorRecord); return false; } @@ -867,14 +901,16 @@ private bool InitProcessPing(String targetNameOrAddress, out string resolvedTarg if (targetAddress == null) { - string message = StringUtil.Format(TestConnectionResources.NoPingResult, - resolvedTargetName, - TestConnectionResources.TargetAddressAbsent); + string message = StringUtil.Format( + TestConnectionResources.NoPingResult, + resolvedTargetName, + TestConnectionResources.TargetAddressAbsent); Exception pingException = new System.Net.NetworkInformation.PingException(message, null); - ErrorRecord errorRecord = new ErrorRecord(pingException, - TestConnectionExceptionId, - ErrorCategory.ResourceUnavailable, - resolvedTargetName); + ErrorRecord errorRecord = new ErrorRecord( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + resolvedTargetName); WriteError(errorRecord); return false; } From a9db3c9e74d099e38e79238755ea45c6adf8f947 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sat, 24 Aug 2019 01:04:59 -0400 Subject: [PATCH 04/59] :art: Code analysis style pass --- .../management/TestConnectionCommand.cs | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 400ae502e82..385661a0e85 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -233,9 +233,8 @@ protected override void ProcessRecord() private void ProcessConnectionByTCPPort(String targetNameOrAddress) { - string resolvedTargetName = null; - IPAddress targetAddress = null; - + string resolvedTargetName; + IPAddress targetAddress; if (!InitProcessPing(targetNameOrAddress, out resolvedTargetName, out targetAddress)) { return; @@ -304,8 +303,10 @@ private void WriteConnectionTestProgress(string targetNameOrAddress, string targ private void WriteConnectionTestFooter() { - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace); - record.RecordType = ProgressRecordType.Completed; + var record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace) + { + RecordType = ProgressRecordType.Completed + }; WriteProgress(record); } @@ -314,10 +315,10 @@ private void WriteConnectionTestFooter() #region TracerouteTest private void ProcessTraceroute(String targetNameOrAddress) { - string resolvedTargetName = null; - IPAddress targetAddress = null; byte[] buffer = GetSendBuffer(BufferSize); + string resolvedTargetName; + IPAddress targetAddress; if (!InitProcessPing(targetNameOrAddress, out resolvedTargetName, out targetAddress)) { return; @@ -420,8 +421,7 @@ private void WriteConsoleTraceRouteHeader(string resolvedTargetName, string targ private void WriteTraceRouteProgress(TraceRouteReply traceRouteReply) { - string msg = string.Empty; - + string msg; if (traceRouteReply.PingReplies[2].Status == IPStatus.TtlExpired || traceRouteReply.PingReplies[2].Status == IPStatus.Success) { @@ -457,8 +457,10 @@ private void WriteTraceRouteFooter() { WriteInformation(TestConnectionResources.TraceRouteComplete, s_PSHostTag); - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace); - record.RecordType = ProgressRecordType.Completed; + var record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace) + { + RecordType = ProgressRecordType.Completed + }; WriteProgress(record); } @@ -532,10 +534,8 @@ internal TraceRouteResult(string source, IPAddress destinationAddress, string de private void ProcessMTUSize(String targetNameOrAddress) { PingReply reply, replyResult = null; - - string resolvedTargetName = null; - IPAddress targetAddress = null; - + string resolvedTargetName; + IPAddress targetAddress; if (!InitProcessPing(targetNameOrAddress, out resolvedTargetName, out targetAddress)) { return; @@ -669,8 +669,10 @@ private void WriteMTUSizeProgress(int currentMTUSize, int retry) private void WriteMTUSizeFooter() { - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace); - record.RecordType = ProgressRecordType.Completed; + var record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace) + { + RecordType = ProgressRecordType.Completed + }; WriteProgress(record); } @@ -680,9 +682,8 @@ private void WriteMTUSizeFooter() private void ProcessPing(String targetNameOrAddress) { - string resolvedTargetName = null; - IPAddress targetAddress = null; - + string resolvedTargetName; + IPAddress targetAddress; if (!InitProcessPing(targetNameOrAddress, out resolvedTargetName, out targetAddress)) { return; @@ -697,8 +698,8 @@ private void ProcessPing(String targetNameOrAddress) byte[] buffer = GetSendBuffer(BufferSize); Ping sender = new Ping(); + PingReply reply; PingOptions pingOptions = new PingOptions(MaxHops, DontFragment.IsPresent); - PingReply reply = null; PingReport pingReport = new PingReport(Source, resolvedTargetName); Int32 timeout = TimeoutSeconds * 1000; Int32 delay = Delay * 1000; @@ -784,7 +785,7 @@ private void WritePingHeader(string resolvedTargetName, string targetAddress) private void WritePingProgress(PingReply reply) { - string msg = string.Empty; + string msg; if (reply.Status != IPStatus.Success) { msg = TestConnectionResources.PingTimeOut; @@ -809,8 +810,10 @@ private void WritePingFooter() { WriteInformation(TestConnectionResources.PingComplete, s_PSHostTag); - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace); - record.RecordType = ProgressRecordType.Completed; + var record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace) + { + RecordType = ProgressRecordType.Completed + }; WriteProgress(record); } @@ -846,10 +849,9 @@ internal PingReport(string source, string destination) private bool InitProcessPing(String targetNameOrAddress, out string resolvedTargetName, out IPAddress targetAddress) { - IPHostEntry hostEntry = null; - resolvedTargetName = targetNameOrAddress; + IPHostEntry hostEntry; if (IPAddress.TryParse(targetNameOrAddress, out targetAddress)) { if (ResolveDestination) From 8954c9e17be4e76a7ee24e950e9a711f3c6fbda1 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sat, 24 Aug 2019 01:07:28 -0400 Subject: [PATCH 05/59] :recycle: Simplify parameter sets --- .../management/TestConnectionCommand.cs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 385661a0e85..7d6d1db7b1b 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -42,31 +42,19 @@ public class TestConnectionCommand : PSCmdlet /// /// Force using IPv4 protocol. /// - [Parameter(ParameterSetName = DefaultPingSet)] - [Parameter(ParameterSetName = RepeatPingSet)] - [Parameter(ParameterSetName = TraceRouteSet)] - [Parameter(ParameterSetName = MtuSizeDetectSet)] - [Parameter(ParameterSetName = TcpPortSet)] + [Parameter()] public SwitchParameter IPv4 { get; set; } /// /// Force using IPv6 protocol. /// - [Parameter(ParameterSetName = DefaultPingSet)] - [Parameter(ParameterSetName = RepeatPingSet)] - [Parameter(ParameterSetName = TraceRouteSet)] - [Parameter(ParameterSetName = MtuSizeDetectSet)] - [Parameter(ParameterSetName = TcpPortSet)] + [Parameter()] public SwitchParameter IPv6 { get; set; } /// /// Do reverse DNS lookup to get names for IP addresses. /// - [Parameter(ParameterSetName = DefaultPingSet)] - [Parameter(ParameterSetName = RepeatPingSet)] - [Parameter(ParameterSetName = TraceRouteSet)] - [Parameter(ParameterSetName = MtuSizeDetectSet)] - [Parameter(ParameterSetName = TcpPortSet)] + [Parameter()] public SwitchParameter ResolveDestination { get; set; } /// From 1ec1d037ad21de4c8ada77eab25afecc1765a375 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sat, 24 Aug 2019 01:40:47 -0400 Subject: [PATCH 06/59] :recycle: Address CodeFactor nits --- .../commands/management/TestConnectionCommand.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 7d6d1db7b1b..8264a154d56 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -42,19 +42,19 @@ public class TestConnectionCommand : PSCmdlet /// /// Force using IPv4 protocol. /// - [Parameter()] + [Parameter] public SwitchParameter IPv4 { get; set; } /// /// Force using IPv6 protocol. /// - [Parameter()] + [Parameter] public SwitchParameter IPv6 { get; set; } /// /// Do reverse DNS lookup to get names for IP addresses. /// - [Parameter()] + [Parameter] public SwitchParameter ResolveDestination { get; set; } /// @@ -130,7 +130,7 @@ public class TestConnectionCommand : PSCmdlet /// Set short output kind ('bool' for Ping, 'int' for MTU size ...). /// Default is to return typed result object(s). /// - [Parameter()] + [Parameter] public SwitchParameter Quiet; /// @@ -139,7 +139,7 @@ public class TestConnectionCommand : PSCmdlet /// It is not the cmdlet timeout! It is a timeout for waiting one ping response. /// The default (from Windows) is 5 second. /// - [Parameter()] + [Parameter] [ValidateRange(ValidateRangeKind.Positive)] public int TimeoutSeconds { get; set; } = 5; From 6c32cac0858f76e21a357617b7f939f291232266 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sat, 24 Aug 2019 01:44:16 -0400 Subject: [PATCH 07/59] :recycle: Address Codacy nits --- .../commands/management/TestConnectionCommand.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 8264a154d56..a16acb0aae4 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -17,11 +17,11 @@ namespace Microsoft.PowerShell.Commands /// The implementation of the "Test-Connection" cmdlet. /// [Cmdlet(VerbsDiagnostic.Test, "Connection", DefaultParameterSetName = DefaultPingSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135266")] - [OutputType(typeof(PingReport), ParameterSetName = new string[] { DefaultPingSet })] - [OutputType(typeof(PingReply), ParameterSetName = new string[] { RepeatPingSet, MtuSizeDetectSet })] - [OutputType(typeof(bool), ParameterSetName = new string[] { DefaultPingSet, RepeatPingSet, TcpPortSet })] - [OutputType(typeof(Int32), ParameterSetName = new string[] { MtuSizeDetectSet })] - [OutputType(typeof(TraceRouteReply), ParameterSetName = new string[] { TraceRouteSet })] + [OutputType(typeof(PingReport), ParameterSetName = new[] { DefaultPingSet })] + [OutputType(typeof(PingReply), ParameterSetName = new[] { RepeatPingSet, MtuSizeDetectSet })] + [OutputType(typeof(bool), ParameterSetName = new[] { DefaultPingSet, RepeatPingSet, TcpPortSet })] + [OutputType(typeof(int), ParameterSetName = new[] { MtuSizeDetectSet })] + [OutputType(typeof(TraceRouteReply), ParameterSetName = new[] { TraceRouteSet })] public class TestConnectionCommand : PSCmdlet { private const string DefaultPingSet = "DefaultPing"; From 140efb2d8c9344450ae8d2b8df79e437069e2f1d Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sat, 24 Aug 2019 08:30:11 -0400 Subject: [PATCH 08/59] :recycle: Simplify type names --- .../management/TestConnectionCommand.cs | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index a16acb0aae4..daa6a22f581 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -16,7 +16,8 @@ namespace Microsoft.PowerShell.Commands /// /// The implementation of the "Test-Connection" cmdlet. /// - [Cmdlet(VerbsDiagnostic.Test, "Connection", DefaultParameterSetName = DefaultPingSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135266")] + [Cmdlet(VerbsDiagnostic.Test, "Connection", DefaultParameterSetName = DefaultPingSet, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135266")] [OutputType(typeof(PingReport), ParameterSetName = new[] { DefaultPingSet })] [OutputType(typeof(PingReply), ParameterSetName = new[] { RepeatPingSet, MtuSizeDetectSet })] [OutputType(typeof(bool), ParameterSetName = new[] { DefaultPingSet, RepeatPingSet, TcpPortSet })] @@ -219,7 +220,7 @@ protected override void ProcessRecord() #region ConnectionTest - private void ProcessConnectionByTCPPort(String targetNameOrAddress) + private void ProcessConnectionByTCPPort(string targetNameOrAddress) { string resolvedTargetName; IPAddress targetAddress; @@ -301,7 +302,7 @@ private void WriteConnectionTestFooter() #endregion ConnectionTest #region TracerouteTest - private void ProcessTraceroute(String targetNameOrAddress) + private void ProcessTraceroute(string targetNameOrAddress) { byte[] buffer = GetSendBuffer(BufferSize); @@ -316,11 +317,11 @@ private void ProcessTraceroute(String targetNameOrAddress) TraceRouteResult traceRouteResult = new TraceRouteResult(Source, targetAddress, resolvedTargetName); - Int32 currentHop = 1; + int currentHop = 1; Ping sender = new Ping(); PingOptions pingOptions = new PingOptions(currentHop, DontFragment.IsPresent); PingReply reply = null; - Int32 timeout = TimeoutSeconds * 1000; + int timeout = TimeoutSeconds * 1000; do { @@ -345,7 +346,7 @@ private void ProcessTraceroute(String targetNameOrAddress) TestConnectionResources.NoPingResult, resolvedTargetName, ex.Message); - Exception pingException = new System.Net.NetworkInformation.PingException(message, ex.InnerException); + Exception pingException = new PingException(message, ex.InnerException); ErrorRecord errorRecord = new ErrorRecord( pingException, TestConnectionExceptionId, @@ -519,7 +520,7 @@ internal TraceRouteResult(string source, IPAddress destinationAddress, string de #endregion TracerouteTest #region MTUSizeTest - private void ProcessMTUSize(String targetNameOrAddress) + private void ProcessMTUSize(string targetNameOrAddress) { PingReply reply, replyResult = null; string resolvedTargetName; @@ -535,7 +536,7 @@ private void ProcessMTUSize(String targetNameOrAddress) int HighMTUSize = 10000; int CurrentMTUSize = 1473; int LowMTUSize = targetAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 1280 : 68; - Int32 timeout = TimeoutSeconds * 1000; + int timeout = TimeoutSeconds * 1000; try { @@ -578,7 +579,7 @@ private void ProcessMTUSize(String targetNameOrAddress) TestConnectionResources.NoPingResult, targetAddress, reply.Status.ToString()); - Exception pingException = new System.Net.NetworkInformation.PingException(message); + Exception pingException = new PingException(message); ErrorRecord errorRecord = new ErrorRecord( pingException, TestConnectionExceptionId, @@ -603,7 +604,7 @@ private void ProcessMTUSize(String targetNameOrAddress) catch (PingException ex) { string message = StringUtil.Format(TestConnectionResources.NoPingResult, targetAddress, ex.Message); - Exception pingException = new System.Net.NetworkInformation.PingException(message, ex.InnerException); + Exception pingException = new PingException(message, ex.InnerException); ErrorRecord errorRecord = new ErrorRecord( pingException, TestConnectionExceptionId, @@ -668,7 +669,7 @@ private void WriteMTUSizeFooter() #region PingTest - private void ProcessPing(String targetNameOrAddress) + private void ProcessPing(string targetNameOrAddress) { string resolvedTargetName; IPAddress targetAddress; @@ -689,8 +690,8 @@ private void ProcessPing(String targetNameOrAddress) PingReply reply; PingOptions pingOptions = new PingOptions(MaxHops, DontFragment.IsPresent); PingReport pingReport = new PingReport(Source, resolvedTargetName); - Int32 timeout = TimeoutSeconds * 1000; - Int32 delay = Delay * 1000; + int timeout = TimeoutSeconds * 1000; + int delay = Delay * 1000; for (int i = 1; i <= Count; i++) { @@ -701,7 +702,7 @@ private void ProcessPing(String targetNameOrAddress) catch (PingException ex) { string message = StringUtil.Format(TestConnectionResources.NoPingResult, resolvedTargetName, ex.Message); - Exception pingException = new System.Net.NetworkInformation.PingException(message, ex.InnerException); + Exception pingException = new PingException(message, ex.InnerException); ErrorRecord errorRecord = new ErrorRecord( pingException, TestConnectionExceptionId, @@ -835,7 +836,7 @@ internal PingReport(string source, string destination) #endregion PingTest - private bool InitProcessPing(String targetNameOrAddress, out string resolvedTargetName, out IPAddress targetAddress) + private bool InitProcessPing(string targetNameOrAddress, out string resolvedTargetName, out IPAddress targetAddress) { resolvedTargetName = targetNameOrAddress; @@ -866,7 +867,7 @@ private bool InitProcessPing(String targetNameOrAddress, out string resolvedTarg TestConnectionResources.NoPingResult, resolvedTargetName, TestConnectionResources.CannotResolveTargetName); - Exception pingException = new System.Net.NetworkInformation.PingException(message, ex); + Exception pingException = new PingException(message, ex); ErrorRecord errorRecord = new ErrorRecord( pingException, TestConnectionExceptionId, @@ -895,7 +896,7 @@ private bool InitProcessPing(String targetNameOrAddress, out string resolvedTarg TestConnectionResources.NoPingResult, resolvedTargetName, TestConnectionResources.TargetAddressAbsent); - Exception pingException = new System.Net.NetworkInformation.PingException(message, null); + Exception pingException = new PingException(message, null); ErrorRecord errorRecord = new ErrorRecord( pingException, TestConnectionExceptionId, From 6ca1f58aed7234490fd28b657d5a1367c89e2abb Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sat, 24 Aug 2019 01:38:16 -0400 Subject: [PATCH 09/59] :recycle: Use single Ping() object for all Send() Rather than instantiate a new Ping() every time, we can store it in a readonly field and just call Send() as needed. --- .../commands/management/TestConnectionCommand.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index daa6a22f581..a25e612c658 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -318,7 +318,6 @@ private void ProcessTraceroute(string targetNameOrAddress) TraceRouteResult traceRouteResult = new TraceRouteResult(Source, targetAddress, resolvedTargetName); int currentHop = 1; - Ping sender = new Ping(); PingOptions pingOptions = new PingOptions(currentHop, DontFragment.IsPresent); PingReply reply = null; int timeout = TimeoutSeconds * 1000; @@ -336,7 +335,7 @@ private void ProcessTraceroute(string targetNameOrAddress) { try { - reply = sender.Send(targetAddress, timeout, buffer, pingOptions); + reply = _sender.Send(targetAddress, timeout, buffer, pingOptions); traceRouteReply.PingReplies.Add(reply); } @@ -540,7 +539,6 @@ private void ProcessMTUSize(string targetNameOrAddress) try { - Ping sender = new Ping(); PingOptions pingOptions = new PingOptions(MaxHops, true); int retry = 1; @@ -556,7 +554,7 @@ private void ProcessMTUSize(string targetNameOrAddress) CurrentMTUSize, HighMTUSize)); - reply = sender.Send(targetAddress, timeout, buffer, pingOptions); + reply = _sender.Send(targetAddress, timeout, buffer, pingOptions); // Cautious! Algorithm is sensitive to changing boundary values. if (reply.Status == IPStatus.PacketTooBig) @@ -686,7 +684,6 @@ private void ProcessPing(string targetNameOrAddress) bool quietResult = true; byte[] buffer = GetSendBuffer(BufferSize); - Ping sender = new Ping(); PingReply reply; PingOptions pingOptions = new PingOptions(MaxHops, DontFragment.IsPresent); PingReport pingReport = new PingReport(Source, resolvedTargetName); @@ -697,7 +694,7 @@ private void ProcessPing(string targetNameOrAddress) { try { - reply = sender.Send(targetAddress, timeout, buffer, pingOptions); + reply = _sender.Send(targetAddress, timeout, buffer, pingOptions); } catch (PingException ex) { @@ -948,6 +945,8 @@ private byte[] GetSendBuffer(int bufferSize) private const int DefaultSendBufferSize = 32; private static byte[] s_DefaultSendBuffer = null; + private readonly Ping _sender = new Ping(); + // Random value for WriteProgress Activity Id. private static readonly int s_ProgressId = 174593053; From 046451fe36fd6685c24f5f5144463832c2b6e33c Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sat, 24 Aug 2019 01:38:47 -0400 Subject: [PATCH 10/59] :sparkles: Add IDisposable implementation --- .../management/TestConnectionCommand.cs | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index a25e612c658..44e1dba2997 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -23,7 +23,7 @@ namespace Microsoft.PowerShell.Commands [OutputType(typeof(bool), ParameterSetName = new[] { DefaultPingSet, RepeatPingSet, TcpPortSet })] [OutputType(typeof(int), ParameterSetName = new[] { MtuSizeDetectSet })] [OutputType(typeof(TraceRouteReply), ParameterSetName = new[] { TraceRouteSet })] - public class TestConnectionCommand : PSCmdlet + public class TestConnectionCommand : PSCmdlet, IDisposable { private const string DefaultPingSet = "DefaultPing"; private const string RepeatPingSet = "RepeatPing"; @@ -936,6 +936,34 @@ private byte[] GetSendBuffer(int bufferSize) return sendBuffer; } + /// + /// IDisposable implementation, dispose of any disposable resources created by the cmdlet. + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// Implementation of IDisposable for both manual Dispose() and finalizer-called disposal of resources. + /// + /// + /// Specified as true when Dispose() was called, false if this is called from the finalizer. + /// + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + _sender.Dispose(); + } + + disposed = true; + } + } + // Count of pings sent per each trace route hop. // Default = 3 (from Windows). // If we change 'DefaultTraceRoutePingCount' we should change 'ConsoleTraceRouteReply' resource string. @@ -945,6 +973,8 @@ private byte[] GetSendBuffer(int bufferSize) private const int DefaultSendBufferSize = 32; private static byte[] s_DefaultSendBuffer = null; + private bool disposed = false; + private readonly Ping _sender = new Ping(); // Random value for WriteProgress Activity Id. @@ -954,5 +984,13 @@ private byte[] GetSendBuffer(int bufferSize) private const string ProgressRecordSpace = " "; private const string TestConnectionExceptionId = "TestConnectionException"; + + /// + /// Finalizer for IDisposable class. + /// + ~TestConnectionCommand() + { + Dispose(disposing: false); + } } } From c99199b5753170e4572483632b21ac13ac11ef31 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sat, 24 Aug 2019 12:29:31 -0400 Subject: [PATCH 11/59] :wrench: Move information stream output to verbose --- .../commands/management/TestConnectionCommand.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 44e1dba2997..4af8d392f00 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -398,14 +398,13 @@ private void WriteConsoleTraceRouteHeader(string resolvedTargetName, string targ targetAddress, MaxHops); - WriteInformation(_testConnectionProgressBarActivity, s_PSHostTag); + WriteVerbose(_testConnectionProgressBarActivity); ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace); WriteProgress(record); } private string _testConnectionProgressBarActivity; - private static string[] s_PSHostTag = new string[] { "PSHOST" }; private void WriteTraceRouteProgress(TraceRouteReply traceRouteReply) { @@ -435,7 +434,7 @@ private void WriteTraceRouteProgress(TraceRouteReply traceRouteReply) msg = StringUtil.Format(TestConnectionResources.TraceRouteTimeOut, traceRouteReply.Hop); } - WriteInformation(msg, s_PSHostTag); + WriteVerbose(msg); ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, msg); WriteProgress(record); @@ -443,7 +442,7 @@ private void WriteTraceRouteProgress(TraceRouteReply traceRouteReply) private void WriteTraceRouteFooter() { - WriteInformation(TestConnectionResources.TraceRouteComplete, s_PSHostTag); + WriteVerbose(TestConnectionResources.TraceRouteComplete); var record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace) { @@ -760,7 +759,7 @@ private void WritePingHeader(string resolvedTargetName, string targetAddress) targetAddress, BufferSize); - WriteInformation(_testConnectionProgressBarActivity, s_PSHostTag); + WriteVerbose(_testConnectionProgressBarActivity); ProgressRecord record = new ProgressRecord( s_ProgressId, @@ -786,7 +785,7 @@ private void WritePingProgress(PingReply reply) reply.Options?.Ttl); } - WriteInformation(msg, s_PSHostTag); + WriteVerbose(msg); ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, msg); WriteProgress(record); @@ -794,7 +793,7 @@ private void WritePingProgress(PingReply reply) private void WritePingFooter() { - WriteInformation(TestConnectionResources.PingComplete, s_PSHostTag); + WriteVerbose(TestConnectionResources.PingComplete); var record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace) { From febe0da19d4588818903a9fef8f4a6bc3f6e34e9 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sat, 24 Aug 2019 13:36:10 -0400 Subject: [PATCH 12/59] :recycle: Rename method a little more simply --- .../commands/management/TestConnectionCommand.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 4af8d392f00..d78e0ff3671 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -313,7 +313,7 @@ private void ProcessTraceroute(string targetNameOrAddress) return; } - WriteConsoleTraceRouteHeader(resolvedTargetName, targetAddress.ToString()); + WriteTraceRouteHeader(resolvedTargetName, targetAddress.ToString()); TraceRouteResult traceRouteResult = new TraceRouteResult(Source, targetAddress, resolvedTargetName); @@ -390,7 +390,7 @@ private void ProcessTraceroute(string targetNameOrAddress) } } - private void WriteConsoleTraceRouteHeader(string resolvedTargetName, string targetAddress) + private void WriteTraceRouteHeader(string resolvedTargetName, string targetAddress) { _testConnectionProgressBarActivity = StringUtil.Format( TestConnectionResources.TraceRouteStart, From d06b9c2a2219f0be2d895d7531935d6c6a25be91 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sat, 24 Aug 2019 14:08:09 -0400 Subject: [PATCH 13/59] :sparkles: Iiplement SendCancellablePing() method. This uses the SendAsync() method with a manual reset shim, allowing us to cancel the ping if needed. For example, in the case of StopProcessing() being called, the cmdlet can properly process the request and halt processing, throwing the correct errors as needed. --- .../management/TestConnectionCommand.cs | 53 +++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index d78e0ff3671..c1484571b8c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -182,7 +182,7 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// protected override void BeginProcessing() { - base.BeginProcessing(); + _sender.PingCompleted += OnPingComplete; switch (ParameterSetName) { @@ -218,6 +218,16 @@ protected override void ProcessRecord() } } + /// + /// On receiving the StopProcessing() request, the cmdlet will immediately cancel any in-progress ping request. + /// This allows a cancellation to occur during a ping request without having to wait for a potentially very + /// long timeout. + /// + protected override void StopProcessing() + { + _sender?.SendAsyncCancel(); + } + #region ConnectionTest private void ProcessConnectionByTCPPort(string targetNameOrAddress) @@ -335,7 +345,7 @@ private void ProcessTraceroute(string targetNameOrAddress) { try { - reply = _sender.Send(targetAddress, timeout, buffer, pingOptions); + reply = SendCancellablePing(targetAddress, timeout, buffer, pingOptions); traceRouteReply.PingReplies.Add(reply); } @@ -553,7 +563,7 @@ private void ProcessMTUSize(string targetNameOrAddress) CurrentMTUSize, HighMTUSize)); - reply = _sender.Send(targetAddress, timeout, buffer, pingOptions); + reply = SendCancellablePing(targetAddress, timeout, buffer, pingOptions); // Cautious! Algorithm is sensitive to changing boundary values. if (reply.Status == IPStatus.PacketTooBig) @@ -693,7 +703,7 @@ private void ProcessPing(string targetNameOrAddress) { try { - reply = _sender.Send(targetAddress, timeout, buffer, pingOptions); + reply = SendCancellablePing(targetAddress, timeout, buffer, pingOptions); } catch (PingException ex) { @@ -957,6 +967,7 @@ protected virtual void Dispose(bool disposing) if (disposing) { _sender.Dispose(); + _pingComplete?.Dispose(); } disposed = true; @@ -975,6 +986,40 @@ protected virtual void Dispose(bool disposing) private bool disposed = false; private readonly Ping _sender = new Ping(); + private readonly ManualResetEventSlim _pingComplete = new ManualResetEventSlim(); + private PingCompletedEventArgs _pingCompleteArgs; + + private PingReply SendCancellablePing( + IPAddress targetAddress, + int timeout, + byte[] buffer, + PingOptions pingOptions) + { + _sender.SendAsync(targetAddress, timeout, buffer, pingOptions, this); + _pingComplete.Wait(); + + // Pause to let _sender's async flags to be reset properly so the next SendAsync call doesn't fail. + Thread.Sleep(1); + + if (_pingCompleteArgs.Cancelled) + { + // The only cancellation we have implemented is on pipeline stops. + throw new PipelineStoppedException(); + } + + if (_pingCompleteArgs.Error != null) + { + throw new PingException(_pingCompleteArgs.Error.Message, _pingCompleteArgs.Error); + } + + return _pingCompleteArgs.Reply; + } + + private static void OnPingComplete(object sender, PingCompletedEventArgs e) + { + ((TestConnectionCommand)e.UserState)._pingCompleteArgs = e; + ((TestConnectionCommand)e.UserState)._pingComplete.Set(); + } // Random value for WriteProgress Activity Id. private static readonly int s_ProgressId = 174593053; From cb50486cd857b0ab04984d5c0318b09641e77081 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sun, 25 Aug 2019 11:26:30 -0400 Subject: [PATCH 14/59] :sparkles: Revamp standard Ping output All output is provided as soon as the PingReply is received, using a new class to format the output (PingStatus). Also added a FormatView for PingStatus. --- .../management/TestConnectionCommand.cs | 161 ++++++++++++++---- .../PowerShellCore_format_ps1xml.cs | 73 ++++++++ 2 files changed, 197 insertions(+), 37 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index c1484571b8c..454a1918170 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -18,7 +18,7 @@ namespace Microsoft.PowerShell.Commands /// [Cmdlet(VerbsDiagnostic.Test, "Connection", DefaultParameterSetName = DefaultPingSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135266")] - [OutputType(typeof(PingReport), ParameterSetName = new[] { DefaultPingSet })] + [OutputType(typeof(PingStatus), ParameterSetName = new[] { DefaultPingSet })] [OutputType(typeof(PingReply), ParameterSetName = new[] { RepeatPingSet, MtuSizeDetectSet })] [OutputType(typeof(bool), ParameterSetName = new[] { DefaultPingSet, RepeatPingSet, TcpPortSet })] [OutputType(typeof(int), ParameterSetName = new[] { MtuSizeDetectSet })] @@ -695,11 +695,10 @@ private void ProcessPing(string targetNameOrAddress) PingReply reply; PingOptions pingOptions = new PingOptions(MaxHops, DontFragment.IsPresent); - PingReport pingReport = new PingReport(Source, resolvedTargetName); int timeout = TimeoutSeconds * 1000; int delay = Delay * 1000; - for (int i = 1; i <= Count; i++) + for (uint i = 1; i <= Count; i++) { try { @@ -733,7 +732,7 @@ private void ProcessPing(string targetNameOrAddress) } else { - pingReport.Replies.Add(reply); + WriteObject(new PingStatus(Source, resolvedTargetName, reply, i)); } WritePingProgress(reply); @@ -755,10 +754,6 @@ private void ProcessPing(string targetNameOrAddress) { WriteObject(quietResult); } - else - { - WriteObject(pingReport); - } } private void WritePingHeader(string resolvedTargetName, string targetAddress) @@ -811,35 +806,6 @@ private void WritePingFooter() }; WriteProgress(record); } - - /// - /// The class contains an information about the source, the destination and ping results. - /// - public class PingReport - { - internal PingReport(string source, string destination) - { - Source = source; - Destination = destination; - Replies = new List(); - } - - /// - /// Source from which to ping. - /// - public string Source { get; } - - /// - /// Destination to which to ping. - /// - public string Destination { get; } - - /// - /// Ping results for every ping attempt. - /// - public List Replies { get; } - } - #endregion PingTest private bool InitProcessPing(string targetNameOrAddress, out string resolvedTargetName, out IPAddress targetAddress) @@ -1029,6 +995,127 @@ private static void OnPingComplete(object sender, PingCompletedEventArgs e) private const string TestConnectionExceptionId = "TestConnectionException"; + /// + /// The class contains information about the source, the destination and ping results. + /// + public class PingStatus + { + /// + /// Initializes a new instance of the class. + /// This constructor allows manually specifying the initial values for the cases where the PingReply + /// object may be missing some information, specifically in the instances where PingReply objects are + /// utilised to perform a traceroute. + /// + /// The source machine name or IP of the ping. + /// The destination machine name of the ping. + /// The response from the ping attempt. + /// The PingOptions specified when the ping was sent. + /// The manually measured latency of the ping attempt. + /// The buffer size. + /// The sequence number in the sequence of pings to the hop point. + internal PingStatus( + string source, + string destination, + PingReply reply, + PingOptions options, + long latency, + int bufferSize, + uint pingNum) + : this(source, destination, reply, pingNum) + { + _options = options; + _latency = latency; + _bufferSize = bufferSize; + } + + /// + /// Initializes a new instance of the class. + /// + /// The source machine name or IP of the ping. + /// The destination machine name of the ping. + /// The response from the ping attempt. + /// The sequence number of the ping in the sequence of pings to the target. + internal PingStatus(string source, string destination, PingReply reply, uint pingNum) + { + Ping = pingNum; + Reply = reply; + Source = source; + Destination = destination ?? reply.Address.ToString(); + } + + // These values should only be set if this PingStatus was created as part of a traceroute. + private readonly int _bufferSize = -1; + private readonly long _latency = -1; + private readonly PingOptions _options; + + /// + /// Gets the sequence number of this ping in the sequence of pings to the + /// + public uint Ping { get; } + + /// + /// Gets the source from which the ping was sent. + /// + public string Source { get; } + + /// + /// Gets the destination which was pinged. + /// + public string Destination { get; } + + /// + /// Gets the target address of the ping. + /// + public IPAddress Address { get => Reply.Status == IPStatus.Success ? Reply.Address : null; } + + /// + /// Gets the roundtrip time of the ping in milliseconds. + /// + public long Latency { get => _latency >= 0 ? _latency : Reply.RoundtripTime; } + + /// + /// Gets the returned status of the ping. + /// + public IPStatus Status { get => Reply.Status; } + + /// + /// Gets the size in bytes of the buffer data sent in the ping. + /// + public int BufferSize { get => _bufferSize >= 0 ? _bufferSize : Reply.Buffer.Length; } + + /// + /// Gets the reply object from this ping. + /// + public PingReply Reply { get; } + + /// + /// Gets the options used when sending the ping. + /// + public PingOptions Options { get => _options ?? Reply.Options; } + } + + /// + /// The class contains information about the source, the destination and ping results. + /// + public class PingMtuStatus : PingStatus + { + /// + /// Initializes a new instance of the class. + /// + /// The source machine name or IP of the ping. + /// The destination machine name of the ping. + /// The response from the ping attempt. + internal PingMtuStatus(string source, string destination, PingReply reply) + : base(source, destination, reply, 1) + { + } + + /// + /// Gets the maximum transmission unit size on the network path between the source and destination. + /// + public int MtuSize { get => BufferSize; } + } + /// /// Finalizer for IDisposable class. /// diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs index 35d34c60670..a9936063df5 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs @@ -255,6 +255,79 @@ internal static IEnumerable GetFormatData() yield return new ExtendedTypeDefinition( "Microsoft.PowerShell.MarkdownRender.PSMarkdownOptionInfo", ViewsOf_Microsoft_PowerShell_MarkdownRender_MarkdownOptionInfo()); + + yield return new ExtendedTypeDefinition( + "Microsoft.PowerShell.Commands.TestConnectionCommand+PingStatus", + ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingStatus()); + + yield return new ExtendedTypeDefinition( + "Microsoft.PowerShell.Commands.TestConnectionCommand+PingMtuStatus", + ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingMtuStatus()); + } + + private static IEnumerable ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingStatus() + { + yield return new FormatViewDefinition( + "Microsoft.PowerShell.Commands.TestConnectionCommand+PingStatus", + TableControl.Create() + .AddHeader(Alignment.Right, label: "Ping", width: 4) + .AddHeader(Alignment.Left, label: "Source", width: 16) + .AddHeader(Alignment.Left, label: "Address", width: 15) + .AddHeader(Alignment.Right, label: "Latency(ms)", width: 7) + .AddHeader(Alignment.Right, label: "BufferSize(B)", width: 10) + .AddHeader(Alignment.Center, label: "Status", width: 16) + .StartRowDefinition() + .AddPropertyColumn("Ping") + .AddPropertyColumn("Source") + .AddScriptBlockColumn(@" + if ($_.Address) { + $_.Address + } + else { + '*' + } + ") + .AddScriptBlockColumn(@" + if ($_.Status -eq 'TimedOut') { + '*' + } + else { + $_.Latency + } + ") + .AddPropertyColumn("BufferSize") + .AddPropertyColumn("Status") + .EndRowDefinition() + .GroupByProperty("Destination") + .EndTable()); + } + + private static IEnumerable ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingMtuStatus() + { + yield return new FormatViewDefinition( + "Microsoft.PowerShell.Commands.TestConnectionCommand+PingMtuStatus", + TableControl.Create() + .AddHeader(Alignment.Left, label: "Source", width: 16) + .AddHeader(Alignment.Left, label: "Address", width: 15) + .AddHeader(Alignment.Right, label: "Latency(ms)", width: 7) + .AddHeader(Alignment.Center, label: "Status", width: 16) + .AddHeader(Alignment.Right, label: "MtuSize(B)", width: 7) + .StartRowDefinition() + .AddPropertyColumn("Source") + .AddPropertyColumn("Address") + .AddScriptBlockColumn(@" + if ($_.Status -eq 'TimedOut') { + '*' + } + else { + $_.Latency + } + ") + .AddPropertyColumn("Status") + .AddPropertyColumn("MtuSize") + .EndRowDefinition() + .GroupByProperty("Destination") + .EndTable()); } private static IEnumerable ViewsOf_System_RuntimeType() From f55c2aa7cc835fb2f8bd8e94e7148997144a9033 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sun, 25 Aug 2019 14:19:17 -0400 Subject: [PATCH 15/59] :sparkles: Improve output for traceroute mode :art: Update formatview definitions to include traceroutes :recycle: move formatviewdefintions to proper location in file --- .../management/TestConnectionCommand.cs | 275 +++++++++++------- .../PowerShellCore_format_ps1xml.cs | 179 +++++++----- 2 files changed, 279 insertions(+), 175 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 454a1918170..61f11b69677 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Management.Automation; using System.Management.Automation.Internal; using System.Net; @@ -22,7 +23,7 @@ namespace Microsoft.PowerShell.Commands [OutputType(typeof(PingReply), ParameterSetName = new[] { RepeatPingSet, MtuSizeDetectSet })] [OutputType(typeof(bool), ParameterSetName = new[] { DefaultPingSet, RepeatPingSet, TcpPortSet })] [OutputType(typeof(int), ParameterSetName = new[] { MtuSizeDetectSet })] - [OutputType(typeof(TraceRouteReply), ParameterSetName = new[] { TraceRouteSet })] + [OutputType(typeof(TraceStatus), ParameterSetName = new[] { TraceRouteSet })] public class TestConnectionCommand : PSCmdlet, IDisposable { private const string DefaultPingSet = "DefaultPing"; @@ -325,29 +326,68 @@ private void ProcessTraceroute(string targetNameOrAddress) WriteTraceRouteHeader(resolvedTargetName, targetAddress.ToString()); - TraceRouteResult traceRouteResult = new TraceRouteResult(Source, targetAddress, resolvedTargetName); - int currentHop = 1; PingOptions pingOptions = new PingOptions(currentHop, DontFragment.IsPresent); PingReply reply = null; int timeout = TimeoutSeconds * 1000; + var timer = new Stopwatch(); do { - TraceRouteReply traceRouteReply = new TraceRouteReply(); - - pingOptions.Ttl = traceRouteReply.Hop = currentHop; + // Clear the stored router name for every hop + string routerName = null; + TraceStatus hopResult = null; + pingOptions.Ttl = currentHop; currentHop++; + var hopReplies = new List(DefaultTraceRoutePingCount); + // In the specific case we don't use 'Count' property. // If we change 'DefaultTraceRoutePingCount' we should change 'ConsoleTraceRouteReply' resource string. - for (int i = 1; i <= DefaultTraceRoutePingCount; i++) + for (uint i = 1; i <= DefaultTraceRoutePingCount; i++) { try { - reply = SendCancellablePing(targetAddress, timeout, buffer, pingOptions); + reply = SendCancellablePing(targetAddress, timeout, buffer, pingOptions, timer); + + // Only get router name if we haven't already retrieved it + if (routerName == null) + { + if (ResolveDestination.IsPresent) + { + try + { + routerName = reply.Status == IPStatus.Success + ? Dns.GetHostEntry(reply.Address).HostName + : reply.Address?.ToString(); + } + catch + { + // Swallow hostname resolution errors and continue with trace + } + } + else + { + routerName = reply.Address?.ToString(); + } + } - traceRouteReply.PingReplies.Add(reply); + var status = new PingStatus( + Source, + routerName, + reply, + pingOptions, + latency: reply.Status == IPStatus.Success ? reply.RoundtripTime : timer.ElapsedMilliseconds, + buffer.Length, + pingNum: i); + hopResult = new TraceStatus(currentHop, status, Source, resolvedTargetName, targetAddress); + + if (!Quiet.IsPresent) + { + WriteObject(hopResult); + } + + timer.Reset(); } catch (PingException ex) { @@ -365,25 +405,14 @@ private void ProcessTraceroute(string targetNameOrAddress) continue; } - catch - { - // Ignore host resolve exceptions. - } + hopReplies.Add(hopResult); // We use short delay because it is impossible DoS with trace route. - Thread.Sleep(200); + Thread.Sleep(50); } - if (ResolveDestination && reply.Status == IPStatus.Success) - { - traceRouteReply.ReplyRouterName = Dns.GetHostEntry(reply.Address).HostName; - } - - traceRouteReply.ReplyRouterAddress = reply.Address; - - WriteTraceRouteProgress(traceRouteReply); + WriteTraceRouteProgress(hopReplies); - traceRouteResult.Replies.Add(traceRouteReply); } while (reply != null && currentHop <= sMaxHops && (reply.Status == IPStatus.TtlExpired || reply.Status == IPStatus.TimedOut)); @@ -394,10 +423,6 @@ private void ProcessTraceroute(string targetNameOrAddress) { WriteObject(currentHop <= sMaxHops); } - else - { - WriteObject(traceRouteResult); - } } private void WriteTraceRouteHeader(string resolvedTargetName, string targetAddress) @@ -416,32 +441,31 @@ private void WriteTraceRouteHeader(string resolvedTargetName, string targetAddre private string _testConnectionProgressBarActivity; - private void WriteTraceRouteProgress(TraceRouteReply traceRouteReply) + private void WriteTraceRouteProgress(IList traceRouteReplies) { string msg; - if (traceRouteReply.PingReplies[2].Status == IPStatus.TtlExpired - || traceRouteReply.PingReplies[2].Status == IPStatus.Success) + if (traceRouteReplies[2].Status == IPStatus.Success) { - var routerAddress = traceRouteReply.ReplyRouterAddress.ToString(); - var routerName = traceRouteReply.ReplyRouterName ?? routerAddress; - var roundtripTime0 = traceRouteReply.PingReplies[0].Status == IPStatus.TimedOut + var routerAddress = traceRouteReplies[2].HopAddress.ToString(); + var routerName = traceRouteReplies[2].Hostname ?? routerAddress; + var roundtripTime0 = traceRouteReplies[0].Status == IPStatus.TimedOut ? "*" - : traceRouteReply.PingReplies[0].RoundtripTime.ToString(); - var roundtripTime1 = traceRouteReply.PingReplies[1].Status == IPStatus.TimedOut + : traceRouteReplies[0].Latency.ToString(); + var roundtripTime1 = traceRouteReplies[1].Status == IPStatus.TimedOut ? "*" - : traceRouteReply.PingReplies[1].RoundtripTime.ToString(); + : traceRouteReplies[1].Latency.ToString(); msg = StringUtil.Format( TestConnectionResources.TraceRouteReply, - traceRouteReply.Hop, + traceRouteReplies[0].Hop, roundtripTime0, roundtripTime1, - traceRouteReply.PingReplies[2].RoundtripTime.ToString(), + traceRouteReplies[2].Latency.ToString(), routerName, routerAddress); } else { - msg = StringUtil.Format(TestConnectionResources.TraceRouteTimeOut, traceRouteReply.Hop); + msg = StringUtil.Format(TestConnectionResources.TraceRouteTimeOut, traceRouteReplies[0].Hop); } WriteVerbose(msg); @@ -461,70 +485,6 @@ private void WriteTraceRouteFooter() WriteProgress(record); } - /// - /// The class contains an information about a trace route attempt. - /// - public class TraceRouteReply - { - internal TraceRouteReply() - { - PingReplies = new List(DefaultTraceRoutePingCount); - } - - /// - /// Number of current hop (router). - /// - public int Hop; - - /// - /// List of ping replies for current hop (router). - /// - public List PingReplies; - - /// - /// Router IP address. - /// - public IPAddress ReplyRouterAddress; - - /// - /// Resolved router name. - /// - public string ReplyRouterName; - } - - /// - /// The class contains an information about the source, the destination and trace route results. - /// - public class TraceRouteResult - { - internal TraceRouteResult(string source, IPAddress destinationAddress, string destinationHost) - { - Source = source; - DestinationAddress = destinationAddress; - DestinationHost = destinationHost; - Replies = new List(); - } - - /// - /// Source from which to trace route. - /// - public string Source { get; } - - /// - /// Destination to which to trace route. - /// - public IPAddress DestinationAddress { get; } - - /// - /// Destination to which to trace route. - /// - public string DestinationHost { get; } - - /// - /// - public List Replies { get; } - } - #endregion TracerouteTest #region MTUSizeTest @@ -959,10 +919,13 @@ private PingReply SendCancellablePing( IPAddress targetAddress, int timeout, byte[] buffer, - PingOptions pingOptions) + PingOptions pingOptions, + Stopwatch timer = null) { + timer?.Start(); _sender.SendAsync(targetAddress, timeout, buffer, pingOptions, this); _pingComplete.Wait(); + timer?.Stop(); // Pause to let _sender's async flags to be reset properly so the next SendAsync call doesn't fail. Thread.Sleep(1); @@ -1010,7 +973,7 @@ public class PingStatus /// The destination machine name of the ping. /// The response from the ping attempt. /// The PingOptions specified when the ping was sent. - /// The manually measured latency of the ping attempt. + /// The latency of the ping. /// The buffer size. /// The sequence number in the sequence of pings to the hop point. internal PingStatus( @@ -1024,8 +987,8 @@ internal PingStatus( : this(source, destination, reply, pingNum) { _options = options; - _latency = latency; _bufferSize = bufferSize; + _latency = latency; } /// @@ -1045,8 +1008,8 @@ internal PingStatus(string source, string destination, PingReply reply, uint pin // These values should only be set if this PingStatus was created as part of a traceroute. private readonly int _bufferSize = -1; - private readonly long _latency = -1; private readonly PingOptions _options; + private readonly long _latency = -1; /// /// Gets the sequence number of this ping in the sequence of pings to the @@ -1116,6 +1079,104 @@ internal PingMtuStatus(string source, string destination, PingReply reply) public int MtuSize { get => BufferSize; } } + /// + /// The class contains an information about a trace route attempt. + /// + public class TraceStatus + { + /// + /// Initializes a new instance of the class. + /// + /// The hop number of this trace hop. + /// The PingStatus response from this trace hop. + /// The source computer name or IP address of the traceroute. + /// The target destination of the traceroute. + /// The target IPAddress of the overall traceroute. + internal TraceStatus( + int hop, + PingStatus status, + string source, + string destination, + IPAddress destinationAddress) + { + _status = status; + Hop = hop; + Source = source; + Target = destination; + TargetAddress = destinationAddress; + } + + private readonly PingStatus _status; + + /// + /// Gets the number of the current hop / router. + /// + public int Hop { get; } + + /// + /// Gets the hostname of the current hop point. + /// + /// + public string Hostname + { + get => _status.Destination != "0.0.0.0" + ? _status.Destination + : null; + } + + /// + /// Gets the sequence number of the ping in the sequence of pings to the hop point. + /// + public uint Ping { get => _status.Ping; } + + /// + /// Gets the IP address of the current hop point. + /// + public IPAddress HopAddress { get => _status.Address; } + + /// + /// Gets the latency values of each ping to the current hop point. + /// + public long Latency { get => _status.Latency; } + + /// + /// Gets the status of the traceroute hop. + /// It is considered successful if the individual ping reports either Success or TtlExpired; + /// TtlExpired is the expected response from an intermediate traceroute hop. + /// + public IPStatus Status + { + get => _status.Status == IPStatus.TtlExpired + ? IPStatus.Success + : _status.Status; + } + + /// + /// Gets the source address of the traceroute command. + /// + public string Source { get; } + + /// + /// Gets the final destination hostname of the trace. + /// + public string Target { get; } + + /// + /// Gets the final destination IP address of the trace. + /// + public IPAddress TargetAddress { get; } + + /// + /// Gets the raw PingReply object received from the ping to this hop point. + /// + public PingReply Reply { get => _status.Reply; } + + /// + /// Gets the PingOptions used to send the ping to the trace hop. + /// + public PingOptions Options { get => _status.Options; } + } + /// /// Finalizer for IDisposable class. /// diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs index a9936063df5..82e9ba39d21 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs @@ -257,77 +257,16 @@ internal static IEnumerable GetFormatData() ViewsOf_Microsoft_PowerShell_MarkdownRender_MarkdownOptionInfo()); yield return new ExtendedTypeDefinition( - "Microsoft.PowerShell.Commands.TestConnectionCommand+PingStatus", - ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingStatus()); - - yield return new ExtendedTypeDefinition( - "Microsoft.PowerShell.Commands.TestConnectionCommand+PingMtuStatus", - ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingMtuStatus()); - } - - private static IEnumerable ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingStatus() - { - yield return new FormatViewDefinition( "Microsoft.PowerShell.Commands.TestConnectionCommand+PingStatus", - TableControl.Create() - .AddHeader(Alignment.Right, label: "Ping", width: 4) - .AddHeader(Alignment.Left, label: "Source", width: 16) - .AddHeader(Alignment.Left, label: "Address", width: 15) - .AddHeader(Alignment.Right, label: "Latency(ms)", width: 7) - .AddHeader(Alignment.Right, label: "BufferSize(B)", width: 10) - .AddHeader(Alignment.Center, label: "Status", width: 16) - .StartRowDefinition() - .AddPropertyColumn("Ping") - .AddPropertyColumn("Source") - .AddScriptBlockColumn(@" - if ($_.Address) { - $_.Address - } - else { - '*' - } - ") - .AddScriptBlockColumn(@" - if ($_.Status -eq 'TimedOut') { - '*' - } - else { - $_.Latency - } - ") - .AddPropertyColumn("BufferSize") - .AddPropertyColumn("Status") - .EndRowDefinition() - .GroupByProperty("Destination") - .EndTable()); - } + ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingStatus()); - private static IEnumerable ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingMtuStatus() - { - yield return new FormatViewDefinition( + yield return new ExtendedTypeDefinition( "Microsoft.PowerShell.Commands.TestConnectionCommand+PingMtuStatus", - TableControl.Create() - .AddHeader(Alignment.Left, label: "Source", width: 16) - .AddHeader(Alignment.Left, label: "Address", width: 15) - .AddHeader(Alignment.Right, label: "Latency(ms)", width: 7) - .AddHeader(Alignment.Center, label: "Status", width: 16) - .AddHeader(Alignment.Right, label: "MtuSize(B)", width: 7) - .StartRowDefinition() - .AddPropertyColumn("Source") - .AddPropertyColumn("Address") - .AddScriptBlockColumn(@" - if ($_.Status -eq 'TimedOut') { - '*' - } - else { - $_.Latency - } - ") - .AddPropertyColumn("Status") - .AddPropertyColumn("MtuSize") - .EndRowDefinition() - .GroupByProperty("Destination") - .EndTable()); + ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingMtuStatus()); + + yield return new ExtendedTypeDefinition( + "Microsoft.PowerShell.Commands.TestConnectionCommand+TraceStatus", + ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_TraceStatus()); } private static IEnumerable ViewsOf_System_RuntimeType() @@ -1511,5 +1450,109 @@ private static IEnumerable ViewsOf_Microsoft_PowerShell_Ma .EndEntry() .EndList()); } + + private static IEnumerable ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingStatus() + { + yield return new FormatViewDefinition( + "Microsoft.PowerShell.Commands.TestConnectionCommand+PingStatus", + TableControl.Create() + .AddHeader(Alignment.Right, label: "Ping", width: 4) + .AddHeader(Alignment.Left, label: "Source", width: 16) + .AddHeader(Alignment.Left, label: "Address", width: 15) + .AddHeader(Alignment.Right, label: "Latency(ms)", width: 7) + .AddHeader(Alignment.Right, label: "BufferSize(B)", width: 10) + .AddHeader(Alignment.Center, label: "Status", width: 16) + .StartRowDefinition() + .AddPropertyColumn("Ping") + .AddPropertyColumn("Source") + .AddScriptBlockColumn(@" + if ($_.Address) { + $_.Address + } + else { + '*' + } + ") + .AddScriptBlockColumn(@" + if ($_.Status -eq 'TimedOut') { + '*' + } + else { + $_.Latency + } + ") + .AddPropertyColumn("BufferSize") + .AddPropertyColumn("Status") + .EndRowDefinition() + .GroupByProperty("Destination") + .EndTable()); + } + + private static IEnumerable ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingMtuStatus() + { + yield return new FormatViewDefinition( + "Microsoft.PowerShell.Commands.TestConnectionCommand+PingMtuStatus", + TableControl.Create() + .AddHeader(Alignment.Left, label: "Source", width: 16) + .AddHeader(Alignment.Left, label: "Address", width: 15) + .AddHeader(Alignment.Right, label: "Latency(ms)", width: 7) + .AddHeader(Alignment.Center, label: "Status", width: 16) + .AddHeader(Alignment.Right, label: "MtuSize(B)", width: 7) + .StartRowDefinition() + .AddPropertyColumn("Source") + .AddPropertyColumn("Address") + .AddScriptBlockColumn(@" + if ($_.Status -eq 'TimedOut') { + '*' + } + else { + $_.Latency + } + ") + .AddPropertyColumn("Status") + .AddPropertyColumn("MtuSize") + .EndRowDefinition() + .GroupByProperty("Destination") + .EndTable()); + } + + private static IEnumerable ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_TraceStatus() + { + yield return new FormatViewDefinition( + "Microsoft.PowerShell.Commands.TestConnectionCommand+TraceStatus", + TableControl.Create() + .AddHeader(Alignment.Right, label: "Hop", width: 3) + .AddHeader(Alignment.Left, label: "Hostname", width: 22) + .AddHeader(Alignment.Right, label: "Ping", width: 4) + .AddHeader(Alignment.Right, label: "Latency(ms)", width: 7) + .AddHeader(Alignment.Center, label: "Status", width: 16) + .AddHeader(Alignment.Left, label: "Source", width: 12) + .AddHeader(Alignment.Left, label: "TargetAddress", width: 15) + .StartRowDefinition() + .AddPropertyColumn("Hop") + .AddScriptBlockColumn(@" + if ($_.Hostname) { + $_.HostName + } + else { + '*' + } + ") + .AddPropertyColumn("Ping") + .AddScriptBlockColumn(@" + if ($_.Status -eq 'TimedOut') { + '*' + } + else { + $_.Latency + } + ") + .AddPropertyColumn("Status") + .AddPropertyColumn("Source") + .AddPropertyColumn("TargetAddress") + .EndRowDefinition() + .GroupByProperty("Target") + .EndTable()); + } } } From 2ee6a8ff7b4ecb2fd247a2bd2a5e340df58265ee Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sun, 25 Aug 2019 14:55:37 -0400 Subject: [PATCH 16/59] :sparkles: Fix -MtuSizeDetect switch output --- .../commands/management/TestConnectionCommand.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 61f11b69677..29b6b6dd23f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -589,17 +589,7 @@ private void ProcessMTUSize(string targetNameOrAddress) } else { - var res = PSObject.AsPSObject(replyResult); - - PSMemberInfo sourceProperty = new PSNoteProperty("Source", Source); - res.Members.Add(sourceProperty); - PSMemberInfo destinationProperty = new PSNoteProperty("Destination", targetNameOrAddress); - res.Members.Add(destinationProperty); - PSMemberInfo mtuSizeProperty = new PSNoteProperty("MTUSize", CurrentMTUSize); - res.Members.Add(mtuSizeProperty); - res.TypeNames.Insert(0, "PingReply#MTUSize"); - - WriteObject(res); + WriteObject(new PingMtuStatus(Source, targetAddress.ToString(), replyResult)); } } From 5805bb8a09413365a4af9e4ff1d6cc28b971cfb9 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sun, 25 Aug 2019 15:03:50 -0400 Subject: [PATCH 17/59] :recycle: Use WriteVerbose instead of Progress bar --- .../management/TestConnectionCommand.cs | 173 ++++-------------- 1 file changed, 39 insertions(+), 134 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 29b6b6dd23f..44374fef952 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -240,7 +240,7 @@ private void ProcessConnectionByTCPPort(string targetNameOrAddress) return; } - WriteConnectionTestHeader(resolvedTargetName, targetAddress.ToString()); + WriteVerboseConnectionTestHeader(resolvedTargetName, targetAddress.ToString()); TcpClient client = new TcpClient(); @@ -251,7 +251,7 @@ private void ProcessConnectionByTCPPort(string targetNameOrAddress) for (var i = 1; i <= TimeoutSeconds; i++) { - WriteConnectionTestProgress(targetNameOrAddress, targetString, i); + WriteVerboseConnectionTest(targetNameOrAddress, targetString, i); Task timeoutTask = Task.Delay(millisecondsDelay: 1000); Task.WhenAny(connectionTask, timeoutTask).Result.Wait(); @@ -277,37 +277,26 @@ private void ProcessConnectionByTCPPort(string targetNameOrAddress) finally { client.Close(); - WriteConnectionTestFooter(); } WriteObject(false); } - private void WriteConnectionTestHeader(string resolvedTargetName, string targetAddress) + private void WriteVerboseConnectionTestHeader(string resolvedTargetName, string targetAddress) { - _testConnectionProgressBarActivity = StringUtil.Format(TestConnectionResources.ConnectionTestStart, resolvedTargetName, targetAddress); - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace); - WriteProgress(record); + WriteVerbose(StringUtil.Format( + TestConnectionResources.ConnectionTestStart, + resolvedTargetName, + targetAddress)); } - private void WriteConnectionTestProgress(string targetNameOrAddress, string targetAddress, int timeout) + private void WriteVerboseConnectionTest(string targetNameOrAddress, string targetAddress, int timeout) { - var msg = StringUtil.Format( + WriteVerbose(StringUtil.Format( TestConnectionResources.ConnectionTestDescription, targetNameOrAddress, targetAddress, - timeout); - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, msg); - WriteProgress(record); - } - - private void WriteConnectionTestFooter() - { - var record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace) - { - RecordType = ProgressRecordType.Completed - }; - WriteProgress(record); + timeout)); } #endregion ConnectionTest @@ -324,7 +313,11 @@ private void ProcessTraceroute(string targetNameOrAddress) return; } - WriteTraceRouteHeader(resolvedTargetName, targetAddress.ToString()); + WriteVerbose(StringUtil.Format( + TestConnectionResources.TraceRouteStart, + resolvedTargetName, + targetAddress.ToString(), + MaxHops)); int currentHop = 1; PingOptions pingOptions = new PingOptions(currentHop, DontFragment.IsPresent); @@ -411,13 +404,13 @@ private void ProcessTraceroute(string targetNameOrAddress) Thread.Sleep(50); } - WriteTraceRouteProgress(hopReplies); + WriteVerboseTraceHop(hopReplies); } while (reply != null && currentHop <= sMaxHops && (reply.Status == IPStatus.TtlExpired || reply.Status == IPStatus.TimedOut)); - WriteTraceRouteFooter(); + WriteVerbose(TestConnectionResources.TraceRouteComplete); if (Quiet.IsPresent) { @@ -425,23 +418,7 @@ private void ProcessTraceroute(string targetNameOrAddress) } } - private void WriteTraceRouteHeader(string resolvedTargetName, string targetAddress) - { - _testConnectionProgressBarActivity = StringUtil.Format( - TestConnectionResources.TraceRouteStart, - resolvedTargetName, - targetAddress, - MaxHops); - - WriteVerbose(_testConnectionProgressBarActivity); - - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace); - WriteProgress(record); - } - - private string _testConnectionProgressBarActivity; - - private void WriteTraceRouteProgress(IList traceRouteReplies) + private void WriteVerboseTraceHop(IList traceRouteReplies) { string msg; if (traceRouteReplies[2].Status == IPStatus.Success) @@ -469,20 +446,6 @@ private void WriteTraceRouteProgress(IList traceRouteReplies) } WriteVerbose(msg); - - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, msg); - WriteProgress(record); - } - - private void WriteTraceRouteFooter() - { - WriteVerbose(TestConnectionResources.TraceRouteComplete); - - var record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace) - { - RecordType = ProgressRecordType.Completed - }; - WriteProgress(record); } #endregion TracerouteTest @@ -498,7 +461,12 @@ private void ProcessMTUSize(string targetNameOrAddress) return; } - WriteMTUSizeHeader(resolvedTargetName, targetAddress.ToString()); + + WriteVerbose(StringUtil.Format( + TestConnectionResources.MTUSizeDetectStart, + resolvedTargetName, + targetAddress.ToString(), + BufferSize)); // Cautious! Algorithm is sensitive to changing boundary values. int HighMTUSize = 10000; @@ -515,7 +483,10 @@ private void ProcessMTUSize(string targetNameOrAddress) { byte[] buffer = GetSendBuffer(CurrentMTUSize); - WriteMTUSizeProgress(CurrentMTUSize, retry); + WriteVerbose(StringUtil.Format( + TestConnectionResources.MTUSizeDetectDescription, + CurrentMTUSize, + retry)); WriteDebug(StringUtil.Format( "LowMTUSize: {0}, CurrentMTUSize: {1}, HighMTUSize: {2}", @@ -581,8 +552,6 @@ private void ProcessMTUSize(string targetNameOrAddress) return; } - WriteMTUSizeFooter(); - if (Quiet.IsPresent) { WriteObject(CurrentMTUSize); @@ -593,35 +562,6 @@ private void ProcessMTUSize(string targetNameOrAddress) } } - private void WriteMTUSizeHeader(string resolvedTargetName, string targetAddress) - { - _testConnectionProgressBarActivity = StringUtil.Format( - TestConnectionResources.MTUSizeDetectStart, - resolvedTargetName, - targetAddress, - BufferSize); - - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace); - WriteProgress(record); - } - - private void WriteMTUSizeProgress(int currentMTUSize, int retry) - { - var msg = StringUtil.Format(TestConnectionResources.MTUSizeDetectDescription, currentMTUSize, retry); - - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, msg); - WriteProgress(record); - } - - private void WriteMTUSizeFooter() - { - var record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace) - { - RecordType = ProgressRecordType.Completed - }; - WriteProgress(record); - } - #endregion MTUSizeTest #region PingTest @@ -637,7 +577,11 @@ private void ProcessPing(string targetNameOrAddress) if (!Continues.IsPresent) { - WritePingHeader(resolvedTargetName, targetAddress.ToString()); + WriteVerbose(StringUtil.Format( + TestConnectionResources.MTUSizeDetectStart, + resolvedTargetName, + targetAddress.ToString(), + BufferSize)); } bool quietResult = true; @@ -685,7 +629,7 @@ private void ProcessPing(string targetNameOrAddress) WriteObject(new PingStatus(Source, resolvedTargetName, reply, i)); } - WritePingProgress(reply); + WriteVerbosePing(reply); } // Delay between ping but not after last ping. @@ -697,7 +641,7 @@ private void ProcessPing(string targetNameOrAddress) if (!Continues.IsPresent) { - WritePingFooter(); + WriteVerbose(TestConnectionResources.PingComplete); } if (Quiet.IsPresent) @@ -706,56 +650,23 @@ private void ProcessPing(string targetNameOrAddress) } } - private void WritePingHeader(string resolvedTargetName, string targetAddress) - { - _testConnectionProgressBarActivity = StringUtil.Format( - TestConnectionResources.MTUSizeDetectStart, - resolvedTargetName, - targetAddress, - BufferSize); - - WriteVerbose(_testConnectionProgressBarActivity); - - ProgressRecord record = new ProgressRecord( - s_ProgressId, - _testConnectionProgressBarActivity, - ProgressRecordSpace); - WriteProgress(record); - } - - private void WritePingProgress(PingReply reply) + private void WriteVerbosePing(PingReply reply) { - string msg; if (reply.Status != IPStatus.Success) { - msg = TestConnectionResources.PingTimeOut; + WriteVerbose(TestConnectionResources.PingTimeOut); } else { - msg = StringUtil.Format( + WriteVerbose(StringUtil.Format( TestConnectionResources.PingReply, reply.Address.ToString(), reply.Buffer.Length, reply.RoundtripTime, - reply.Options?.Ttl); + reply.Options?.Ttl)); } - - WriteVerbose(msg); - - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, msg); - WriteProgress(record); } - private void WritePingFooter() - { - WriteVerbose(TestConnectionResources.PingComplete); - - var record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace) - { - RecordType = ProgressRecordType.Completed - }; - WriteProgress(record); - } #endregion PingTest private bool InitProcessPing(string targetNameOrAddress, out string resolvedTargetName, out IPAddress targetAddress) @@ -940,12 +851,6 @@ private static void OnPingComplete(object sender, PingCompletedEventArgs e) ((TestConnectionCommand)e.UserState)._pingComplete.Set(); } - // Random value for WriteProgress Activity Id. - private static readonly int s_ProgressId = 174593053; - - // Empty message string for Progress Bar. - private const string ProgressRecordSpace = " "; - private const string TestConnectionExceptionId = "TestConnectionException"; /// From fbc189c4e78003d869d648428728f40366dea711 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sun, 25 Aug 2019 16:27:11 -0400 Subject: [PATCH 18/59] :art: :memo: Whitespace and comment changes --- .../management/TestConnectionCommand.cs | 90 ++++++++++--------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 44374fef952..4c76ffc3dce 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -35,33 +35,34 @@ public class TestConnectionCommand : PSCmdlet, IDisposable #region Parameters /// - /// Do ping test. + /// Gets or sets whether to do ping test. + /// Default is true. /// [Parameter(ParameterSetName = DefaultPingSet)] [Parameter(ParameterSetName = RepeatPingSet)] public SwitchParameter Ping { get; set; } = true; /// - /// Force using IPv4 protocol. + /// Gets or sets whether to force use of IPv4 protocol. /// [Parameter] public SwitchParameter IPv4 { get; set; } /// - /// Force using IPv6 protocol. + /// Gets or sets whether to force use of IPv6 protocol. /// [Parameter] public SwitchParameter IPv6 { get; set; } /// - /// Do reverse DNS lookup to get names for IP addresses. + /// Gets or sets whether to do reverse DNS lookup to get names for IP addresses. /// [Parameter] public SwitchParameter ResolveDestination { get; set; } /// - /// Source from which to do a test (ping, trace route, ...). - /// The default is Local Host. + /// Gets the source from which to run the selected test. + /// The default is localhost. /// Remoting is not yet implemented internally in the cmdlet. /// [Parameter(ParameterSetName = DefaultPingSet)] @@ -71,9 +72,9 @@ public class TestConnectionCommand : PSCmdlet, IDisposable public string Source { get; } = Dns.GetHostName(); /// - /// The number of times the Ping data packets can be forwarded by routers. - /// As gateways and routers transmit packets through a network, - /// they decrement the Time-to-Live (TTL) value found in the packet header. + /// Gets or sets the number of times the Ping data packets can be forwarded by routers. + /// As gateways and routers transmit packets through a network, they decrement the Time-to-Live (TTL) + /// value found in the packet header. /// The default (from Windows) is 128 hops. /// [Parameter(ParameterSetName = DefaultPingSet)] @@ -86,7 +87,7 @@ public class TestConnectionCommand : PSCmdlet, IDisposable private const int sMaxHops = 128; /// - /// Count of attempts. + /// Gets or sets the number of ping attempts. /// The default (from Windows) is 4 times. /// [Parameter(ParameterSetName = DefaultPingSet)] @@ -94,7 +95,7 @@ public class TestConnectionCommand : PSCmdlet, IDisposable public int Count { get; set; } = 4; /// - /// Delay between attempts. + /// Gets or sets the delay between ping attempts. /// The default (from Windows) is 1 second. /// [Parameter(ParameterSetName = DefaultPingSet)] @@ -103,9 +104,9 @@ public class TestConnectionCommand : PSCmdlet, IDisposable public int Delay { get; set; } = 1; /// - /// Buffer size to send. - /// The default (from Windows) is 32 bites. - /// Max value is 65500 (limit from Windows API). + /// Gets or sets the buffer size to send with the ping packet. + /// The default (from Windows) is 32 bytes. + /// Max value is 65500 (limitation imposed by Windows API). /// [Parameter(ParameterSetName = DefaultPingSet)] [Parameter(ParameterSetName = RepeatPingSet)] @@ -114,7 +115,7 @@ public class TestConnectionCommand : PSCmdlet, IDisposable public int BufferSize { get; set; } = DefaultSendBufferSize; /// - /// Don't fragment ICMP packages. + /// Gets or sets whether to prevent fragmentation of the ICMP packets. /// Currently CoreFX not supports this on Unix. /// [Parameter(ParameterSetName = DefaultPingSet)] @@ -122,31 +123,30 @@ public class TestConnectionCommand : PSCmdlet, IDisposable public SwitchParameter DontFragment { get; set; } /// - /// Continue ping until user press Ctrl-C - /// or Int.MaxValue threshold reached. + /// Gets or sets whether to continue pinging until user presses Ctrl-C (or Int.MaxValue threshold reached). /// [Parameter(ParameterSetName = RepeatPingSet)] public SwitchParameter Continues { get; set; } /// - /// Set short output kind ('bool' for Ping, 'int' for MTU size ...). - /// Default is to return typed result object(s). + /// Gets or sets whether to enable quiet output mode, reducing output to a single simple value only. + /// By default, objects are emitted. + /// With this switch, standard ping and -Traceroute returns only true / false, and -MtuSize returns an integer. /// [Parameter] public SwitchParameter Quiet; /// - /// Time-out value in seconds. + /// Gets or sets the timeout value for an individual ping in seconds. /// If a response is not received in this time, no response is assumed. - /// It is not the cmdlet timeout! It is a timeout for waiting one ping response. - /// The default (from Windows) is 5 second. + /// The default (from Windows) is 5 seconds. /// [Parameter] [ValidateRange(ValidateRangeKind.Positive)] public int TimeoutSeconds { get; set; } = 5; /// - /// Destination - computer name or IP address. + /// Gets or sets the destination hostname or IP address. /// [Parameter( Mandatory = true, @@ -158,19 +158,21 @@ public class TestConnectionCommand : PSCmdlet, IDisposable public string[] TargetName { get; set; } /// - /// Detect MTU size. + /// Gets or sets whether to detect Maximum Transmission Unit size. + /// When selected, only a single ping result is returned, indicating the maximum buffer size + /// the route to the destination can support without fragmenting the ICMP packets. /// [Parameter(Mandatory = true, ParameterSetName = MtuSizeDetectSet)] public SwitchParameter MTUSizeDetect { get; set; } /// - /// Do traceroute test. + /// Gets or sets whether to perform a traceroute test. /// [Parameter(Mandatory = true, ParameterSetName = TraceRouteSet)] public SwitchParameter Traceroute { get; set; } /// - /// Do tcp connection test. + /// Gets or sets whether to perform a TCP connection test. /// [ValidateRange(0, 65535)] [Parameter(Mandatory = true, ParameterSetName = TcpPortSet)] @@ -179,10 +181,11 @@ public class TestConnectionCommand : PSCmdlet, IDisposable #endregion Parameters /// - /// Init the cmdlet. + /// BeginProcessing implementation for TestConnectionCommand. /// protected override void BeginProcessing() { + // Add the event handler to the PingCompleted event, to inform the cmdlet when pings are completed. _sender.PingCompleted += OnPingComplete; switch (ParameterSetName) @@ -221,8 +224,7 @@ protected override void ProcessRecord() /// /// On receiving the StopProcessing() request, the cmdlet will immediately cancel any in-progress ping request. - /// This allows a cancellation to occur during a ping request without having to wait for a potentially very - /// long timeout. + /// This allows a cancellation to occur during a ping request without having to wait for the timeout. /// protected override void StopProcessing() { @@ -335,7 +337,7 @@ private void ProcessTraceroute(string targetNameOrAddress) var hopReplies = new List(DefaultTraceRoutePingCount); - // In the specific case we don't use 'Count' property. + // In traceroutes we don't use 'Count' parameter. // If we change 'DefaultTraceRoutePingCount' we should change 'ConsoleTraceRouteReply' resource string. for (uint i = 1; i <= DefaultTraceRoutePingCount; i++) { @@ -400,7 +402,8 @@ private void ProcessTraceroute(string targetNameOrAddress) } hopReplies.Add(hopResult); - // We use short delay because it is impossible DoS with trace route. + + // We use a short delay because it is impossible to DoS with traceroute. Thread.Sleep(50); } @@ -461,14 +464,13 @@ private void ProcessMTUSize(string targetNameOrAddress) return; } - WriteVerbose(StringUtil.Format( TestConnectionResources.MTUSizeDetectStart, resolvedTargetName, targetAddress.ToString(), BufferSize)); - // Cautious! Algorithm is sensitive to changing boundary values. + // Caution! Algorithm is sensitive to changing boundary values. int HighMTUSize = 10000; int CurrentMTUSize = 1473; int LowMTUSize = targetAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 1280 : 68; @@ -496,7 +498,6 @@ private void ProcessMTUSize(string targetNameOrAddress) reply = SendCancellablePing(targetAddress, timeout, buffer, pingOptions); - // Cautious! Algorithm is sensitive to changing boundary values. if (reply.Status == IPStatus.PacketTooBig) { HighMTUSize = CurrentMTUSize; @@ -510,7 +511,7 @@ private void ProcessMTUSize(string targetNameOrAddress) } else { - // Target host don't reply - try again up to 'Count'. + // If the host did't reply, try again up to the 'Count' value. if (retry >= Count) { string message = StringUtil.Format( @@ -632,7 +633,7 @@ private void ProcessPing(string targetNameOrAddress) WriteVerbosePing(reply); } - // Delay between ping but not after last ping. + // Delay between pings, but not after last ping. if (i < Count && Delay > 0) { Thread.Sleep(delay); @@ -749,7 +750,7 @@ private bool InitProcessPing(string targetNameOrAddress, out string resolvedTarg } // Users most often use the default buffer size so we cache the buffer. - // Creates and filles a send buffer. This follows the ping.exe and CoreFX model. + // Creates and fills a send buffer. This follows the ping.exe and CoreFX model. private byte[] GetSendBuffer(int bufferSize) { if (bufferSize == DefaultSendBufferSize && s_DefaultSendBuffer != null) @@ -801,21 +802,24 @@ protected virtual void Dispose(bool disposing) } } - // Count of pings sent per each trace route hop. - // Default = 3 (from Windows). - // If we change 'DefaultTraceRoutePingCount' we should change 'ConsoleTraceRouteReply' resource string. + // Count of pings sent to each trace route hop. Default mimics Windows' defaults. + // If this value changes, we need to update 'ConsoleTraceRouteReply' resource string. private const int DefaultTraceRoutePingCount = 3; - /// Create the default send buffer once and cache it. + // Default size for the send buffer. private const int DefaultSendBufferSize = 32; + private static byte[] s_DefaultSendBuffer = null; private bool disposed = false; private readonly Ping _sender = new Ping(); + private readonly ManualResetEventSlim _pingComplete = new ManualResetEventSlim(); + private PingCompletedEventArgs _pingCompleteArgs; + // Uses the SendAsync() method to send pings, so that Ctrl+C can halt the request early if needed. private PingReply SendCancellablePing( IPAddress targetAddress, int timeout, @@ -833,7 +837,7 @@ private PingReply SendCancellablePing( if (_pingCompleteArgs.Cancelled) { - // The only cancellation we have implemented is on pipeline stops. + // The only cancellation we have implemented is on pipeline stops via StopProcessing(). throw new PipelineStoppedException(); } @@ -845,6 +849,8 @@ private PingReply SendCancellablePing( return _pingCompleteArgs.Reply; } + // This event is triggered when the ping is completed, and passes along the eventargs so that we know + // if the ping was cancelled, or an exception was thrown. private static void OnPingComplete(object sender, PingCompletedEventArgs e) { ((TestConnectionCommand)e.UserState)._pingCompleteArgs = e; From c13f08c27f8d97c31cd79d7c17f3b507320ac770 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sun, 25 Aug 2019 18:12:25 -0400 Subject: [PATCH 19/59] :recycle: Refactor parameters --- .../management/TestConnectionCommand.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 4c76ffc3dce..da6cb0e1f24 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -125,8 +125,9 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// /// Gets or sets whether to continue pinging until user presses Ctrl-C (or Int.MaxValue threshold reached). /// - [Parameter(ParameterSetName = RepeatPingSet)] - public SwitchParameter Continues { get; set; } + [Parameter(Mandatory = true, ParameterSetName = RepeatPingSet)] + [Alias("Continues", "Continuous")] + public SwitchParameter Repeat { get; set; } /// /// Gets or sets whether to enable quiet output mode, reducing output to a single simple value only. @@ -163,7 +164,8 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// the route to the destination can support without fragmenting the ICMP packets. /// [Parameter(Mandatory = true, ParameterSetName = MtuSizeDetectSet)] - public SwitchParameter MTUSizeDetect { get; set; } + [Alias("MtuSizeDetect")] + public SwitchParameter MtuSize { get; set; } /// /// Gets or sets whether to perform a traceroute test. @@ -176,7 +178,7 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// [ValidateRange(0, 65535)] [Parameter(Mandatory = true, ParameterSetName = TcpPortSet)] - public int TCPPort { get; set; } + public int TcpPort { get; set; } #endregion Parameters @@ -248,7 +250,7 @@ private void ProcessConnectionByTCPPort(string targetNameOrAddress) try { - Task connectionTask = client.ConnectAsync(targetAddress, TCPPort); + Task connectionTask = client.ConnectAsync(targetAddress, TcpPort); string targetString = targetAddress.ToString(); for (var i = 1; i <= TimeoutSeconds; i++) @@ -576,7 +578,7 @@ private void ProcessPing(string targetNameOrAddress) return; } - if (!Continues.IsPresent) + if (!Repeat.IsPresent) { WriteVerbose(StringUtil.Format( TestConnectionResources.MTUSizeDetectStart, @@ -614,7 +616,7 @@ private void ProcessPing(string targetNameOrAddress) continue; } - if (Continues.IsPresent) + if (Repeat.IsPresent) { WriteObject(reply); } @@ -640,7 +642,7 @@ private void ProcessPing(string targetNameOrAddress) } } - if (!Continues.IsPresent) + if (!Repeat.IsPresent) { WriteVerbose(TestConnectionResources.PingComplete); } From 67c407d947445b13717f7633859f172e3d532310 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sun, 25 Aug 2019 19:19:08 -0400 Subject: [PATCH 20/59] :recycle: Organise private fields into regions :recycle: Small amount of cleanup --- .../management/TestConnectionCommand.cs | 70 ++++++++++--------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index da6cb0e1f24..0a8f60f82a0 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -26,12 +26,43 @@ namespace Microsoft.PowerShell.Commands [OutputType(typeof(TraceStatus), ParameterSetName = new[] { TraceRouteSet })] public class TestConnectionCommand : PSCmdlet, IDisposable { + #region ParameterSetName Constants + private const string DefaultPingSet = "DefaultPing"; private const string RepeatPingSet = "RepeatPing"; private const string TraceRouteSet = "TraceRoute"; private const string TcpPortSet = "TcpPort"; private const string MtuSizeDetectSet = "MtuSizeDetect"; + #endregion + + #region Cmdlet Defaults + + // Count of pings sent to each trace route hop. Default mimics Windows' defaults. + // If this value changes, we need to update 'ConsoleTraceRouteReply' resource string. + private const int DefaultTraceRoutePingCount = 3; + + // Default size for the send buffer. + private const int DefaultSendBufferSize = 32; + + private const string TestConnectionExceptionId = "TestConnectionException"; + + #endregion + + #region Private Fields + + private static byte[] s_DefaultSendBuffer = null; + + private bool disposed = false; + + private readonly Ping _sender = new Ping(); + + private readonly ManualResetEventSlim _pingComplete = new ManualResetEventSlim(); + + private PingCompletedEventArgs _pingCompleteArgs; + + #endregion + #region Parameters /// @@ -237,9 +268,7 @@ protected override void StopProcessing() private void ProcessConnectionByTCPPort(string targetNameOrAddress) { - string resolvedTargetName; - IPAddress targetAddress; - if (!InitProcessPing(targetNameOrAddress, out resolvedTargetName, out targetAddress)) + if (!InitProcessPing(targetNameOrAddress, out string resolvedTargetName, out IPAddress targetAddress)) { return; } @@ -310,9 +339,7 @@ private void ProcessTraceroute(string targetNameOrAddress) { byte[] buffer = GetSendBuffer(BufferSize); - string resolvedTargetName; - IPAddress targetAddress; - if (!InitProcessPing(targetNameOrAddress, out resolvedTargetName, out targetAddress)) + if (!InitProcessPing(targetNameOrAddress, out string resolvedTargetName, out IPAddress targetAddress)) { return; } @@ -333,7 +360,6 @@ private void ProcessTraceroute(string targetNameOrAddress) { // Clear the stored router name for every hop string routerName = null; - TraceStatus hopResult = null; pingOptions.Ttl = currentHop; currentHop++; @@ -343,6 +369,7 @@ private void ProcessTraceroute(string targetNameOrAddress) // If we change 'DefaultTraceRoutePingCount' we should change 'ConsoleTraceRouteReply' resource string. for (uint i = 1; i <= DefaultTraceRoutePingCount; i++) { + TraceStatus hopResult; try { reply = SendCancellablePing(targetAddress, timeout, buffer, pingOptions, timer); @@ -459,9 +486,7 @@ private void WriteVerboseTraceHop(IList traceRouteReplies) private void ProcessMTUSize(string targetNameOrAddress) { PingReply reply, replyResult = null; - string resolvedTargetName; - IPAddress targetAddress; - if (!InitProcessPing(targetNameOrAddress, out resolvedTargetName, out targetAddress)) + if (!InitProcessPing(targetNameOrAddress, out string resolvedTargetName, out IPAddress targetAddress)) { return; } @@ -571,9 +596,7 @@ private void ProcessMTUSize(string targetNameOrAddress) private void ProcessPing(string targetNameOrAddress) { - string resolvedTargetName; - IPAddress targetAddress; - if (!InitProcessPing(targetNameOrAddress, out resolvedTargetName, out targetAddress)) + if (!InitProcessPing(targetNameOrAddress, out string resolvedTargetName, out IPAddress targetAddress)) { return; } @@ -792,7 +815,7 @@ public void Dispose() /// protected virtual void Dispose(bool disposing) { - if (!this.disposed) + if (!disposed) { if (disposing) { @@ -804,23 +827,6 @@ protected virtual void Dispose(bool disposing) } } - // Count of pings sent to each trace route hop. Default mimics Windows' defaults. - // If this value changes, we need to update 'ConsoleTraceRouteReply' resource string. - private const int DefaultTraceRoutePingCount = 3; - - // Default size for the send buffer. - private const int DefaultSendBufferSize = 32; - - private static byte[] s_DefaultSendBuffer = null; - - private bool disposed = false; - - private readonly Ping _sender = new Ping(); - - private readonly ManualResetEventSlim _pingComplete = new ManualResetEventSlim(); - - private PingCompletedEventArgs _pingCompleteArgs; - // Uses the SendAsync() method to send pings, so that Ctrl+C can halt the request early if needed. private PingReply SendCancellablePing( IPAddress targetAddress, @@ -859,8 +865,6 @@ private static void OnPingComplete(object sender, PingCompletedEventArgs e) ((TestConnectionCommand)e.UserState)._pingComplete.Set(); } - private const string TestConnectionExceptionId = "TestConnectionException"; - /// /// The class contains information about the source, the destination and ping results. /// From 8622cac0a39f423c60064a426bfdbf726ee4792a Mon Sep 17 00:00:00 2001 From: "Joel Sallow (/u/ta11ow)" <32407840+vexx32@users.noreply.github.com> Date: Thu, 3 Oct 2019 17:18:59 -0400 Subject: [PATCH 21/59] messy rebase changes goin' in (#4) --- .devcontainer/Dockerfile | 2 +- .devcontainer/devcontainer.json | 2 +- .../ISSUE_TEMPLATE/Distribution_Request.md | 3 +- .github/ISSUE_TEMPLATE/Support_Question.md | 3 +- .spelling | 9 + .vsts-ci/install-ps.yml | 56 +- .vsts-ci/templates/install-ps-phase.yml | 8 +- CHANGELOG.md | 93 +++ README.md | 62 +- .../InstallPSCorePolicyDefinitions.ps1 | 88 +++ .../PowerShellCoreExecutionPolicy.adml | 125 ++++ .../PowerShellCoreExecutionPolicy.admx | 119 ++++ assets/Product.wxs | 26 +- assets/files.wxs | 32 +- build.psm1 | 57 +- docs/learning-powershell/README.md | 8 +- global.json | 2 +- ...soft.PowerShell.Commands.Management.csproj | 2 +- .../commands/management/GetContentCommand.cs | 15 +- .../commands/management/Service.cs | 13 +- .../management/TestConnectionCommand.cs | 231 +++----- .../management/WriteContentCommandBase.cs | 4 +- .../resources/TestConnectionResources.resx | 36 -- ...crosoft.PowerShell.Commands.Utility.csproj | 6 +- .../commands/utility/AddType.cs | 20 +- .../commands/utility/ConvertTo-Html.cs | 2 +- .../commands/utility/DebugRunspaceCommand.cs | 22 +- .../EnableDisableRunspaceDebugCommand.cs | 22 - .../commands/utility/GetRandomCommand.cs | 16 +- .../commands/utility/New-PSBreakpoint.cs | 101 ---- .../commands/utility/Set-PSBreakpoint.cs | 20 +- .../commands/utility/Var.cs | 4 +- .../host/msh/ConsoleHost.cs | 4 +- ...crosoft.PowerShell.CoreCLR.Eventing.csproj | 2 +- .../GlobalToolShim.cs | 11 +- .../Microsoft.PowerShell.SDK.csproj | 22 +- .../security/CertificateCommands.cs | 2 +- .../Microsoft.WSMan.Management.csproj | 2 +- src/Modules/PSGalleryModules.csproj | 3 +- .../Microsoft.PowerShell.Utility.psd1 | 6 +- .../Microsoft.PowerShell.Utility.psd1 | 12 +- .../Certificate_format_ps1xml.cs | 3 +- .../FileSystem_format_ps1xml.cs | 3 +- .../PowerShellCore_format_ps1xml.cs | 3 +- .../Registry_format_ps1xml.cs | 5 +- .../common/BaseOutputtingCommand.cs | 1 + .../FormatAndOutput/out-console/OutConsole.cs | 6 +- .../System.Management.Automation.csproj | 22 +- .../engine/ArgumentTypeConverterAttribute.cs | 5 +- .../engine/AutomationEngine.cs | 38 +- .../engine/CommandBase.cs | 2 +- .../CommandCompletion/CompletionCompleters.cs | 7 + .../engine/ExecutionContext.cs | 30 +- .../ExperimentalFeature.cs | 5 +- .../engine/InitialSessionState.cs | 14 +- .../engine/InternalCommands.cs | 161 ++++-- .../engine/Modules/ModuleCmdletBase.cs | 4 +- .../engine/Modules/ScriptAnalysis.cs | 2 + .../engine/MshCommandRuntime.cs | 16 +- .../engine/PSVersionInfo.cs | 6 + .../engine/Utils.cs | 113 +++- .../engine/debugger/Breakpoint.cs | 16 +- .../engine/debugger/debugger.cs | 541 +++++++++--------- .../engine/hostifaces/HostUtilities.cs | 18 +- .../engine/hostifaces/PSTask.cs | 123 ++-- .../hostifaces/PowerShellProcessInstance.cs | 42 +- .../engine/parser/AstVisitor.cs | 12 +- .../engine/parser/CharTraits.cs | 21 +- .../engine/parser/Compiler.cs | 10 + .../engine/parser/ConstantValues.cs | 50 +- .../engine/parser/Parser.cs | 219 ++++++- .../engine/parser/PreOrderVisitor.cs | 3 + .../engine/parser/SafeValues.cs | 171 ++++-- .../engine/parser/TypeInferenceVisitor.cs | 5 + .../engine/parser/VariableAnalysis.cs | 79 ++- .../engine/parser/ast.cs | 100 +++- .../engine/parser/token.cs | 10 +- .../engine/parser/tokenizer.cs | 40 +- .../engine/remoting/client/Job.cs | 34 +- .../engine/remoting/client/JobManager.cs | 76 ++- .../engine/remoting/client/remoterunspace.cs | 242 +++++++- .../engine/remoting/commands/DebugJob.cs | 9 +- .../engine/remoting/commands/StartJob.cs | 20 + .../remoting/common/RunspaceConnectionInfo.cs | 6 + .../common/WireDataFormat/EncodeAndDecode.cs | 2 +- .../RemoteDebuggingCapability.cs | 118 ++++ .../fanin/InitialSessionStateProvider.cs | 6 +- .../fanin/OutOfProcTransportManager.cs | 3 +- .../server/OutOfProcServerMediator.cs | 58 +- .../server/ServerRunspacePoolDriver.cs | 326 ++++++++--- .../remoting/server/serverremotesession.cs | 38 +- .../help/HelpErrorTracer.cs | 11 +- .../help/MUIFileSearcher.cs | 4 +- .../namespaces/FileSystemContentStream.cs | 63 +- .../namespaces/FileSystemProvider.cs | 46 +- .../resources/DebuggerStrings.resx | 3 + .../resources/ErrorPackage.resx | 15 +- .../resources/FileSystemProviderStrings.resx | 2 +- .../resources/InternalCommandStrings.resx | 3 + .../resources/ParserStrings.resx | 3 + .../resources/RemotingErrorIdStrings.resx | 3 + .../security/SecuritySupport.cs | 3 + .../utils/FuzzyMatch.cs | 7 +- .../utils/Telemetry.cs | 12 +- .../Install-PowerShellRemoting.ps1 | 4 +- src/powershell-unix/powershell-unix.csproj | 1 - .../powershell-win-core.csproj | 3 +- test/hosting/test_HostingBasic.cs | 35 ++ test/powershell/Host/ConsoleHost.Tests.ps1 | 4 + test/powershell/Host/Startup.Tests.ps1 | 5 + .../Operators/TernaryOperator.Tests.ps1 | 111 ++++ .../Language/Parser/Parsing.Tests.ps1 | 110 ++++ .../Scripting/ActionPreference.Tests.ps1 | 237 +++++--- .../ForEach-Object.Tests.ps1 | 50 ++ .../Get-Item.Tests.ps1 | 9 + .../Remove-Item.Tests.ps1 | 8 +- .../Set-Service.Tests.ps1 | 3 + .../Test-Connection.Tests.ps1 | 7 +- .../EnableDisablePSBreakpoint.Tests.ps1 | 23 - .../Format-Custom.Tests.ps1 | 2 + .../Get-Date.Tests.ps1 | 7 +- .../Get-Random.Tests.ps1 | 12 + .../New-PSBreakpoint.Tests.ps1 | 195 ------- .../MOF-Compilation.Tests.ps1 | 11 +- .../PSDesiredStateConfiguration.Tests.ps1 | 535 +++++++++++++++++ .../configuration.Tests.ps1 | 38 ++ test/powershell/SDK/Breakpoint.Tests.ps1 | 169 ++++++ .../engine/Basic/DefaultCommands.Tests.ps1 | 1 - .../Basic/GroupPolicySettings.Tests.ps1 | 248 ++++++++ test/powershell/engine/Job/Jobs.Tests.ps1 | 20 + .../engine/Remoting/PSSession.Tests.ps1 | 5 + .../Remoting/RemoteSession.Basic.Tests.ps1 | 15 + .../Modules/HelpersCommon/HelpersCommon.psd1 | 1 + .../Modules/HelpersCommon/HelpersCommon.psm1 | 18 + test/tools/TestMetadata.json | 1 - test/tools/WebListener/WebListener.csproj | 4 +- test/xUnit/csharp/test_Runspace.cs | 26 - tools/install-powershell.ps1 | 80 ++- tools/metadata.json | 10 +- tools/packaging/packaging.psm1 | 31 +- ...crosoft.PowerShell.Commands.Utility.csproj | 2 +- .../System.Management.Automation.csproj | 4 +- .../releaseBuild/azureDevOps/releaseBuild.yml | 9 + .../templates/SetVersionVariables.yml | 8 +- .../azureDevOps/templates/json.yml | 45 ++ .../azureDevOps/templates/linux.yml | 16 +- .../templates/mac-package-signing.yml | 28 +- .../azureDevOps/templates/nuget.yml | 6 +- .../azureDevOps/templates/shouldSign.yml | 17 + .../azureDevOps/templates/upload.yml | 6 +- .../azureDevOps/templates/windows-build.yml | 15 +- .../templates/windows-package-signing.yml | 9 +- tools/releaseBuild/setReleaseTag.ps1 | 76 ++- tools/releaseBuild/signing.xml | 1 + 154 files changed, 4748 insertions(+), 1825 deletions(-) create mode 100644 assets/GroupPolicy/InstallPSCorePolicyDefinitions.ps1 create mode 100644 assets/GroupPolicy/PowerShellCoreExecutionPolicy.adml create mode 100644 assets/GroupPolicy/PowerShellCoreExecutionPolicy.admx delete mode 100644 src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs create mode 100644 src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteDebuggingCapability.cs create mode 100644 test/powershell/Language/Operators/TernaryOperator.Tests.ps1 create mode 100644 test/powershell/Modules/Microsoft.PowerShell.Core/ForEach-Object.Tests.ps1 delete mode 100644 test/powershell/Modules/Microsoft.PowerShell.Utility/EnableDisablePSBreakpoint.Tests.ps1 delete mode 100644 test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1 create mode 100644 test/powershell/Modules/PSDesiredStateConfiguration/PSDesiredStateConfiguration.Tests.ps1 create mode 100644 test/powershell/Modules/PSDesiredStateConfiguration/configuration.Tests.ps1 create mode 100644 test/powershell/SDK/Breakpoint.Tests.ps1 create mode 100644 test/powershell/engine/Basic/GroupPolicySettings.Tests.ps1 create mode 100644 tools/releaseBuild/azureDevOps/templates/json.yml create mode 100644 tools/releaseBuild/azureDevOps/templates/shouldSign.yml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ab9dd52ad5d..f936207545e 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,7 +3,7 @@ # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. #------------------------------------------------------------------------------------------------------------- -FROM mcr.microsoft.com/dotnet/core/sdk:3.0.100-preview8 +FROM mcr.microsoft.com/dotnet/core/sdk:3.0.100 # Avoid warnings by switching to noninteractive ENV DEBIAN_FRONTEND=noninteractive diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4574e9ef709..3dafe5a50c0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ // See https://aka.ms/vscode-remote/devcontainer.json for format details. { - "name": ".NET Core 3.0-preview8, including pwsh (Debian 10)", + "name": ".NET Core 3.0, including pwsh (Debian 10)", "dockerFile": "Dockerfile", // Uncomment the next line to run commands after the container is created. diff --git a/.github/ISSUE_TEMPLATE/Distribution_Request.md b/.github/ISSUE_TEMPLATE/Distribution_Request.md index 53fd8a81e5b..ac545ce2d75 100644 --- a/.github/ISSUE_TEMPLATE/Distribution_Request.md +++ b/.github/ISSUE_TEMPLATE/Distribution_Request.md @@ -11,9 +11,10 @@ assignees: '' - Name of the Distribution: - Version of the Distribution: +- Processor Architecture (One per request): - [ ] The version of the Distribution is supported for at least one year. - [ ] The version of the Distribution is not an [interim release](https://ubuntu.com/about/release-cycle) or equivalent. -- [ ] The version of the Distribution is [supported by .NET Core](https://github.com/dotnet/core/blob/master/release-notes/3.0/3.0-supported-os.md#linux). +- [ ] The version and architecture of the Distribution is [supported by .NET Core](https://github.com/dotnet/core/blob/master/release-notes/3.0/3.0-supported-os.md#linux). - [ ] An issues has been filed to create a Docker image in https://github.com/powershell/powershell-docker ## Progress - For PowerShell Team **ONLY** diff --git a/.github/ISSUE_TEMPLATE/Support_Question.md b/.github/ISSUE_TEMPLATE/Support_Question.md index 4c25c372f23..434fb3a752e 100644 --- a/.github/ISSUE_TEMPLATE/Support_Question.md +++ b/.github/ISSUE_TEMPLATE/Support_Question.md @@ -16,7 +16,8 @@ assignees: '' ## Community Resources [Slack][powershell-slack] and [Discord][powershell-discord] Community Chat - Interactive chat with other PowerShell enthusiasts. Both Slack and Discord are bridged via a bot and can seamlessly talk to each other. -[PowerShell.org Forum](https://powershell.org/forums/) - Search or post new general PowerShell usage questions + +[StackOverflow.com](https://stackoverflow.com/questions/tagged/powershell) and [PowerShell.org Forum](https://powershell.org/forums/) - Search or post new general PowerShell usage questions [powershell-slack]: https://join.slack.com/t/powershell/shared_invite/enQtMzA3MDcxNTM5MTkxLTBmMWIyNzhkYzVjNGRiOTgxZmFlN2E0ZmVmOWU5NDczNTY2NDFhZjFlZTM1MTZiMWIzZDcwMGYzNjQ3YTRkNWM [powershell-discord]: https://discordapp.com/invite/AtzXnJM diff --git a/.spelling b/.spelling index 05b0c3e5fb7..cbeddc42690 100644 --- a/.spelling +++ b/.spelling @@ -111,6 +111,7 @@ codepage commanddiscovery commandsearch comobject +composability config connect-pssession consolehost @@ -320,6 +321,7 @@ interactivetesting interop interoperation invoke-cimmethod +Invoke-DSCResource invoke-restmethod invoke-wsmanaction iot @@ -467,6 +469,7 @@ offthewoll oising omi omnisharp +OneDrive oneget.org opencover opencover.zip @@ -611,6 +614,7 @@ robo210 ronn rpalo runspace +runspaces runspaceinit runtime runtimes @@ -762,6 +766,7 @@ v6.1.1 v6.2.0 v6.2.1 v6.2.2 +v6.2.3 validatenotnullorempty versioned versioning @@ -840,6 +845,8 @@ CodeFormatter StyleCop SytzeAndr yashrajbharti +Leonhardt +tylerleonhardt - CHANGELOG.md aavdberg asrosent @@ -868,6 +875,8 @@ uninstallation vongrippen yurko7 zhenggu +davinci26 +vedhasp analytics - docs/debugging/README.md corehost diff --git a/.vsts-ci/install-ps.yml b/.vsts-ci/install-ps.yml index 9b8a1e23072..72f42551162 100644 --- a/.vsts-ci/install-ps.yml +++ b/.vsts-ci/install-ps.yml @@ -46,7 +46,7 @@ phases: parameters: scriptName: sudo ./tools/install-powershell.sh jobName: InstallPowerShellUbuntu - pool: Hosted Ubuntu 1604 + pool: ubuntu-latest verification: | if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"6.2.0") { @@ -57,7 +57,7 @@ phases: parameters: scriptName: sudo ./tools/install-powershell.sh jobName: InstallPowerShellAmazonLinux - pool: Hosted Ubuntu 1604 + pool: ubuntu-latest container: pshorg/powershellcommunity-test-deps:amazonlinux-2.0 verification: | if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"6.2.0") @@ -69,7 +69,7 @@ phases: parameters: scriptName: sudo ./tools/installpsh-amazonlinux.sh jobName: InstallPSHAmazonLinux - pool: Hosted Ubuntu 1604 + pool: ubuntu-latest container: pshorg/powershellcommunity-test-deps:amazonlinux-2.0 verification: | if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"6.2.0") @@ -83,14 +83,14 @@ phases: parameters: scriptName: ./tools/install-powershell.sh jobName: InstallPowerShellCentOS - pool: Hosted Ubuntu 1604 + pool: ubuntu-latest container: mcr.microsoft.com/powershell/test-deps:centos-7 - + - template: templates/install-ps-phase.yml parameters: scriptName: ./tools/install-powershell.sh jobName: InstallPowerShellDebian9 - pool: Hosted Ubuntu 1604 + pool: ubuntu-latest container: mcr.microsoft.com/powershell/test-deps:debian-9 @@ -103,7 +103,7 @@ phases: parameters: scriptName: ./tools/install-powershell.sh jobName: InstallPowerShellMacOS - pool: Hosted macOS + pool: macOS-latest verification: | if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"6.2.0") { @@ -115,4 +115,44 @@ phases: parameters: scriptName: pwsh -c ./tools/install-powershell.ps1 -AddToPath jobName: InstallPowerShellPS1Ubuntu - pool: Hosted Ubuntu 1604 + pool: ubuntu-latest + +- template: templates/install-ps-phase.yml + parameters: + scriptName: pwsh -c ./tools/install-powershell.ps1 -AddToPath -Daily + jobName: InstallPowerShellPS1UbuntuDaily + pool: ubuntu-latest + verification: | + Write-Verbose $PSVersionTable.PSVersion -verbose + if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"7.0.0") + { + throw "powershell was not upgraded: $($PSVersionTable.PSVersion)" + } + +- template: templates/install-ps-phase.yml + parameters: + scriptName: pwsh -c ./tools/install-powershell.ps1 -AddToPath -Daily + jobName: InstallPowerShellMacOSDaily + pool: macOS-latest + verification: | + Write-Verbose $PSVersionTable.PSVersion -verbose + if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"7.0.0") + { + throw "powershell was not upgraded: $($PSVersionTable.PSVersion)" + } + +- template: templates/install-ps-phase.yml + parameters: + scriptName: | + pwsh -c ./tools/install-powershell.ps1 -AddToPath -Daily + jobName: InstallPowerShellWindowsDaily + pool: windows-latest + verification: | + $newVersion = &$env:LOCALAPPDATA\Microsoft\powershell-daily\pwsh -v + $newVersion -match '^PowerShell ((\d*\.\d*\.\d*)(-\w*(\.\d*)?)?){1}' + $versionOnly = $Matches[2] + Write-verbose "$newVersion; versionOnly: $versionOnly" -verbose + if ([Version]$versionOnly -lt [version]"7.0.0") + { + throw "powershell was not upgraded: $newVersion" + } diff --git a/.vsts-ci/templates/install-ps-phase.yml b/.vsts-ci/templates/install-ps-phase.yml index f864be7a492..62b7553f832 100644 --- a/.vsts-ci/templates/install-ps-phase.yml +++ b/.vsts-ci/templates/install-ps-phase.yml @@ -1,5 +1,5 @@ parameters: - pool: 'Hosted Ubuntu 1604' + pool: 'ubuntu-latest' jobName: 'none' scriptName: '' container: '' @@ -16,12 +16,12 @@ jobs: container: ${{ parameters.container }} pool: - name: ${{ parameters.pool }} + vmImage: ${{ parameters.pool }} displayName: ${{ parameters.jobName }} steps: - - powershell: | + - pwsh: | Get-ChildItem -Path env: displayName: Capture environment condition: succeededOrFailed() @@ -37,6 +37,6 @@ jobs: continueOnError: ${{ parameters.continueOnError }} - ${{ if ne(parameters.verification, '') }}: - - powershell: ${{ parameters.verification }} + - pwsh: ${{ parameters.verification }} displayName: Verification continueOnError: ${{ parameters.continueOnError }} diff --git a/CHANGELOG.md b/CHANGELOG.md index a356d830a0a..ef8b3d0ddb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,77 @@ # Changelog +## v7.0.0-preview.4 - 09/19/2019 + +### Engine Updates and Fixes + +- Add support to `ActionPreference.Break` to break into debugger when `Debug`, `Error`, `Information`, `Progress`, `Verbose` or `Warning` messages are generated (#8205) (Thanks @KirkMunro!) +- Enable starting control panel add-ins within PowerShell Core without specifying `.CPL` extension. (#9828) + +### Performance + +- Make `ForEach-Object` faster for its commonly used scenarios (#10454) and fix `ForEach-Object -Parallel` performance problem with many runspaces (#10455) + +### Experimental Features + +- Update `PSDesiredStateConfiguration` module version to `2.0.3` and bring new tests; enable compilation to MOF on non-Windows and use of Invoke-DSCResource without LCM (#10516) +- Add APIs for breakpoint management in runspaces and enable attach to process without `BreakAll` for PowerShell Editor Services (#10338) (Thanks @KirkMunro!) +- Support [ternary operator](https://github.com/PowerShell/PowerShell-RFC/pull/218) in PowerShell language (#10367) + +### General Cmdlet Updates and Fixes + +- Add PowerShell Core group policy definitions (#10468) +- Update console host to support `XTPUSHSGR`/`XTPOPSGR` VT control sequences that are used in [composability scenarios](https://github.com/microsoft/terminal/issues/1796). (#10208) +- Add `WorkingDirectory` parameter to `Start-Job` (#10324) (Thanks @davinci26!) +- Remove the event handler that was causing breakpoint changes to be erroneously replicated to the host runspace debugger (#10503) (Thanks @KirkMunro!) +- Replace `api-ms-win-core-job-12-1-0.dll` with `Kernell32.dll` in `Microsoft.PowerShell.Commands.NativeMethods` P/Invoke API(#10417) (Thanks @iSazonov!) +- Fix wrong output for `New-Service` in variable assignment and `-OutVariable` (#10444) (Thanks @kvprasoon!) +- Fix global tool issues around exit code, command line parameters and path with spaces (#10461) +- Fix recursion into OneDrive - change `FindFirstFileEx()` to use `SafeFindHandle` type (#10405) +- Skip auto-loading `PSReadLine` on Windows if the [NVDA screen reader](https://www.nvaccess.org/about-nvda/) is active (#10385) +- Increase built-with-PowerShell module versions to `7.0.0.0` (#10356) +- Add throwing an error in `Add-Type` if a type with the same name already exists (#9609) (Thanks @iSazonov!) + +### Code Cleanup + +- Convert `ActionPreference.Suspend` enumeration value into a non-supported, reserved state, and remove restriction on using `ActionPreference.Ignore` in preference variables (#10317) (Thanks @KirkMunro!) +- Replace `ArrayList` with `List` to get more readable and reliable code without changing functionality (#10333) (Thanks @iSazonov!) +- Make code style fixes to `TestConnectionCommand` (#10439) (Thanks @vexx32!) +- Cleanup `AutomationEngine` and remove extra `SetSessionStateDrive` method call (#10416) (Thanks @iSazonov!) +- Rename default `ParameterSetName` back to `Delimiter` for `ConvertTo-Csv` and `ConvertFrom-Csv` (#10425) + +### Tools + +- Update `install-powershell.ps1` to check for already installed daily build (#10489) + +### Tests + +- Add experimental check to `ForEach-Object -Parallel` tests (#10354) (Thanks @KirkMunro!) +- Update tests for Alpine validation (#10428) + +### Build and Packaging Improvements + +- Bump `PowerShellGet` version from `2.2` to `2.2.1` (#10382) +- Bump `PackageManagement` version from `1.4.3` to `1.4.4` (#10383) +- Update `README.md` and `metadata.json` for `7.0.0-preview.4` (Internal 10011) +- Upgrade `.Net Core 3.0` version from `Preview 9` to `RC1` (#10552) (Thanks @bergmeister!) +- Fix `ExperimentalFeature` list generation (Internal 9996) +- Bump `PSReadLine` version from `2.0.0-beta4` to `2.0.0-beta5` (#10536) +- Fix release build script to set release tag +- Update version of `Microsoft.PowerShell.Native` to `7.0.0-preview.2` (#10519) +- Upgrade to `Netcoreapp3.0 preview9` (#10484) (Thanks @bergmeister!) +- Make sure the daily coordinated build, knows it is a daily build (#10464) +- Update the combined package build to release the daily builds (#10449) +- Remove appveyor reference (#10445) (Thanks @RDIL!) +- Bump `NJsonSchema` version from `10.0.22` to `10.0.23` (#10421) +- Remove the deletion of `linux-x64` build folder because some dependencies for Alpine need it (#10407) + +### Documentation and Help Content + +- Update `README.md` and metadata for `v6.1.6` and `v6.2.3` releases (#10523) +- Fix a typo in `README.md` (#10465) (Thanks @vedhasp!) +- Add a reference to `PSKoans` module to Learning Resources documentation (#10369) (Thanks @vexx32!) +- Update `README.md` and `metadata.json` for `7.0.0-preview.3` (#10393) + ## v7.0.0-preview.3 - 08/20/2019 ### Breaking Changes @@ -457,6 +529,21 @@ - Update docs for `6.2.0-rc.1` release (#9022) - Update release template (#8996) +## v6.2.3 - 09/12/2019 + +### Engine Updates and Fixes + +- Fix debugger performance regression in system lock down mode (#10269) + +### Tests + +- Remove `markdownlint` tests due to security issues (#10163) + +### Build and Packaging Improvements + +- Update DotNet SDK and runtime framework version (Internal 9946) +- Fix macOS build break (#10207) + ## v6.2.2 - 07/16/2019 ### Breaking Changes @@ -1221,6 +1308,12 @@ - Update `CONTRIBUTION.md` about adding an empty line after the copyright header (#7706) (Thanks @iSazonov!) - Update docs about .NET Core version `2.0` to be about version `2.x` (#7467) (Thanks @bergmeister!) +## 6.1.6 - 2019-09-12 + +### Build and Packaging Improvements + +- Update DotNet SDK and runtime framework version (Internal 9945) + ## 6.1.5 - 2019-07-16 ### Breaking changes diff --git a/README.md b/README.md index e4b647d9f3f..e7ef20ecd03 100644 --- a/README.md +++ b/README.md @@ -57,38 +57,38 @@ You can also download the PowerShell binary archives for Windows, macOS and Linu | Windows (arm) **Experimental** | [32-bit][rl-winarm]/[64-bit][rl-winarm64] | [32-bit][pv-winarm]/[64-bit][pv-winarm64] | [Instructions][in-arm] | | Raspbian (Stretch) **Experimental** | [32-bit][rl-raspbian]/[64-bit][rl-raspbian64] | [32-bit][pv-arm32]/[64-bit][pv-arm64] | [Instructions][in-raspbian] | -[rl-windows-64]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.2/PowerShell-6.2.2-win-x64.msi -[rl-windows-86]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.2/PowerShell-6.2.2-win-x86.msi -[rl-ubuntu18]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.2/powershell_6.2.2-1.ubuntu.18.04_amd64.deb -[rl-ubuntu16]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.2/powershell_6.2.2-1.ubuntu.16.04_amd64.deb -[rl-debian9]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.2/powershell_6.2.2-1.debian.9_amd64.deb -[rl-centos]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.2/powershell-6.2.2-1.rhel.7.x86_64.rpm -[rl-macos]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.2/powershell-6.2.2-osx-x64.pkg -[rl-winarm]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.2/PowerShell-6.2.2-win-arm32.zip -[rl-winarm64]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.2/PowerShell-6.2.2-win-arm64.zip -[rl-winx86-zip]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.2/PowerShell-6.2.2-win-x86.zip -[rl-winx64-zip]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.2/PowerShell-6.2.2-win-x64.zip -[rl-macos-tar]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.2/powershell-6.2.2-osx-x64.tar.gz -[rl-linux-tar]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.2/powershell-6.2.2-linux-x64.tar.gz -[rl-raspbian]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.2/powershell-6.2.2-linux-arm32.tar.gz -[rl-raspbian64]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.2/powershell-6.2.2-linux-arm64.tar.gz +[rl-windows-64]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.3/PowerShell-6.2.3-win-x64.msi +[rl-windows-86]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.3/PowerShell-6.2.3-win-x86.msi +[rl-ubuntu18]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.3/powershell_6.2.3-1.ubuntu.18.04_amd64.deb +[rl-ubuntu16]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.3/powershell_6.2.3-1.ubuntu.16.04_amd64.deb +[rl-debian9]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.3/powershell_6.2.3-1.debian.9_amd64.deb +[rl-centos]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.3/powershell-6.2.3-1.rhel.7.x86_64.rpm +[rl-macos]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.3/powershell-6.2.3-osx-x64.pkg +[rl-winarm]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.3/PowerShell-6.2.3-win-arm32.zip +[rl-winarm64]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.3/PowerShell-6.2.3-win-arm64.zip +[rl-winx86-zip]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.3/PowerShell-6.2.3-win-x86.zip +[rl-winx64-zip]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.3/PowerShell-6.2.3-win-x64.zip +[rl-macos-tar]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.3/powershell-6.2.3-osx-x64.tar.gz +[rl-linux-tar]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.3/powershell-6.2.3-linux-x64.tar.gz +[rl-raspbian]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.3/powershell-6.2.3-linux-arm32.tar.gz +[rl-raspbian64]: https://github.com/PowerShell/PowerShell/releases/download/v6.2.3/powershell-6.2.3-linux-arm64.tar.gz [rl-snap]: https://snapcraft.io/powershell -[pv-windows-64]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.3/PowerShell-7.0.0-preview.3-win-x64.msi -[pv-windows-86]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.3/PowerShell-7.0.0-preview.3-win-x86.msi -[pv-ubuntu18]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.3/powershell-preview_7.0.0-preview.3-1.ubuntu.18.04_amd64.deb -[pv-ubuntu16]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.3/powershell-preview_7.0.0-preview.3-1.ubuntu.16.04_amd64.deb -[pv-debian9]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.3/powershell-preview_7.0.0-preview.3-1.debian.9_amd64.deb -[pv-centos]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.3/powershell-preview-7.0.0_preview.3-1.rhel.7.x86_64.rpm -[pv-macos]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.3/powershell-7.0.0-preview.3-osx-x64.pkg -[pv-winarm]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.3/PowerShell-7.0.0-preview.3-win-arm32.zip -[pv-winarm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.3/PowerShell-7.0.0-preview.3-win-arm64.zip -[pv-winx86-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.3/PowerShell-7.0.0-preview.3-win-x86.zip -[pv-winx64-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.3/PowerShell-7.0.0-preview.3-win-x64.zip -[pv-macos-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.3/powershell-7.0.0-preview.3-osx-x64.tar.gz -[pv-linux-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.3/powershell-7.0.0-preview.3-linux-x64.tar.gz -[pv-arm32]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.3/powershell-7.0.0-preview.3-linux-arm32.tar.gz -[pv-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.3/powershell-7.0.0-preview.3-linux-arm64.tar.gz +[pv-windows-64]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.4/PowerShell-7.0.0-preview.4-win-x64.msi +[pv-windows-86]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.4/PowerShell-7.0.0-preview.4-win-x86.msi +[pv-ubuntu18]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.4/powershell-preview_7.0.0-preview.4-1.ubuntu.18.04_amd64.deb +[pv-ubuntu16]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.4/powershell-preview_7.0.0-preview.4-1.ubuntu.16.04_amd64.deb +[pv-debian9]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.4/powershell-preview_7.0.0-preview.4-1.debian.9_amd64.deb +[pv-centos]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.4/powershell-preview-7.0.0_preview.4-1.rhel.7.x86_64.rpm +[pv-macos]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.4/powershell-7.0.0-preview.4-osx-x64.pkg +[pv-winarm]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.4/PowerShell-7.0.0-preview.4-win-arm32.zip +[pv-winarm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.4/PowerShell-7.0.0-preview.4-win-arm64.zip +[pv-winx86-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.4/PowerShell-7.0.0-preview.4-win-x86.zip +[pv-winx64-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.4/PowerShell-7.0.0-preview.4-win-x64.zip +[pv-macos-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.4/powershell-7.0.0-preview.4-osx-x64.tar.gz +[pv-linux-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.4/powershell-7.0.0-preview.4-linux-x64.tar.gz +[pv-arm32]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.4/powershell-7.0.0-preview.4-linux-arm32.tar.gz +[pv-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-preview.4/powershell-7.0.0-preview.4-linux-arm64.tar.gz [pv-snap]: https://snapcraft.io/powershell-preview [in-windows]: https://docs.microsoft.com/powershell/scripting/setup/installing-powershell-core-on-windows?view=powershell-6 @@ -219,7 +219,7 @@ License: By requesting and using the Container OS Image for Windows containers, By default, PowerShell collects the OS description and the version of PowerShell (equivalent to `$PSVersionTable.OS` and `$PSVersionTable.GitCommitId`) using [Application Insights](https://azure.microsoft.com/services/application-insights/). To opt-out of sending telemetry, create an environment variable called `POWERSHELL_TELEMETRY_OPTOUT` set to a value of `1` before starting PowerShell from the installed location. -The telemetry we collect fall under the [Microsoft Privacy Statement](https://privacy.microsoft.com/privacystatement/). +The telemetry we collect falls under the [Microsoft Privacy Statement](https://privacy.microsoft.com/privacystatement/). ## Governance diff --git a/assets/GroupPolicy/InstallPSCorePolicyDefinitions.ps1 b/assets/GroupPolicy/InstallPSCorePolicyDefinitions.ps1 new file mode 100644 index 00000000000..9634059bec0 --- /dev/null +++ b/assets/GroupPolicy/InstallPSCorePolicyDefinitions.ps1 @@ -0,0 +1,88 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.Synopsis + Group Policy tools use administrative template files (.admx, .adml) to populate policy settings in the user interface. + This allows administrators to manage registry-based policy settings. + This script installes PowerShell Core Administrative Templates for Windows. +.Notes + The PowerShellCoreExecutionPolicy.admx and PowerShellCoreExecutionPolicy.adml files are + expected to be at the location specified by the Path parameter with default value of the location of this script. +#> +[CmdletBinding()] +param +( + [ValidateNotNullOrEmpty()] + [string] $Path = $PSScriptRoot +) +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Test-Elevated +{ + [CmdletBinding()] + [OutputType([bool])] + Param() + + # if the current Powershell session was called with administrator privileges, + # the Administrator Group's well-known SID will show up in the Groups for the current identity. + # Note that the SID won't show up unless the process is elevated. + return (([Security.Principal.WindowsIdentity]::GetCurrent()).Groups -contains "S-1-5-32-544") +} +$IsWindowsOs = $PSHOME.EndsWith('\WindowsPowerShell\v1.0', [System.StringComparison]::OrdinalIgnoreCase) -or $IsWindows + +if (-not $IsWindowsOs) +{ + throw 'This script must be run on Windows.' +} + +if (-not (Test-Elevated)) +{ + throw 'This script must be run from an elevated process.' +} + +if ([System.Management.Automation.Platform]::IsNanoServer) +{ + throw 'Group policy definitions are not supported on Nano Server.' +} + +$admxName = 'PowerShellCoreExecutionPolicy.admx' +$admlName = 'PowerShellCoreExecutionPolicy.adml' +$admx = Get-Item -Path (Join-Path -Path $Path -ChildPath $admxName) +$adml = Get-Item -Path (Join-Path -Path $Path -ChildPath $admlName) +$admxTargetPath = Join-Path -Path $env:WINDIR -ChildPath "PolicyDefinitions" +$admlTargetPath = Join-Path -Path $admxTargetPath -ChildPath "en-US" + +$files = @($admx, $adml) +foreach ($file in $files) +{ + if (-not (Test-Path -Path $file)) + { + throw "Could not find $($file.Name) at $Path" + } +} + +Write-Verbose "Copying $admx to $admxTargetPath" +Copy-Item -Path $admx -Destination $admxTargetPath -Force +$admxTargetFullPath = Join-Path -Path $admxTargetPath -ChildPath $admxName +if (Test-Path -Path $admxTargetFullPath) +{ + Write-Verbose "$admxName was installed successfully" +} +else +{ + Write-Error "Could not install $admxName" +} + +Write-Verbose "Copying $adml to $admlTargetPath" +Copy-Item -Path $adml -Destination $admlTargetPath -Force +$admlTargetFullPath = Join-Path -Path $admlTargetPath -ChildPath $admlName +if (Test-Path -Path $admlTargetFullPath) +{ + Write-Verbose "$admlName was installed successfully" +} +else +{ + Write-Error "Could not install $admlName" +} diff --git a/assets/GroupPolicy/PowerShellCoreExecutionPolicy.adml b/assets/GroupPolicy/PowerShellCoreExecutionPolicy.adml new file mode 100644 index 00000000000..3068ae57a24 --- /dev/null +++ b/assets/GroupPolicy/PowerShellCoreExecutionPolicy.adml @@ -0,0 +1,125 @@ + + + PowerShell Core + This file contains the configuration options for PowerShell Core + + + Allow all scripts + Allow only signed scripts + Turn on Script Execution + This policy setting lets you configure the script execution policy, controlling which scripts are allowed to run. + +If you enable this policy setting, the scripts selected in the drop-down list are allowed to run. + +The "Allow only signed scripts" policy setting allows scripts to execute only if they are signed by a trusted publisher. + +The "Allow local scripts and remote signed scripts" policy setting allows any local scrips to run; scripts that originate from the internet must be signed by a trusted publisher. + +The "Allow all scripts" policy setting allows all scripts to run. + +If you disable this policy setting, no scripts are allowed to run. + +Note: This policy setting exists under both "Computer Configuration" and "User Configuration" in the Local Group Policy Editor. The "Computer Configuration" has precedence over "User Configuration." + +If you disable or do not configure this policy setting, it reverts to a per-machine preference setting; the default if that is not configured is "Allow local scripts and remote signed scripts." + PowerShell Core + Allow local scripts and remote signed scripts + At least Microsoft Windows 7 or Windows Server 2008 family + + Turn on Module Logging + + This policy setting allows you to turn on logging for PowerShell Core modules. + + If you enable this policy setting, pipeline execution events for members of the specified modules are recorded in the PowerShell Core log in Event Viewer. Enabling this policy setting for a module is equivalent to setting the LogPipelineExecutionDetails property of the module to True. + + If you disable this policy setting, logging of execution events is disabled for all PowerShell Core modules. Disabling this policy setting for a module is equivalent to setting the LogPipelineExecutionDetails property of the module to False. + + If this policy setting is not configured, the LogPipelineExecutionDetails property of a module determines whether the execution events of a module are logged. By default, the LogPipelineExecutionDetails property of all modules is set to False. + + To add modules to the policy setting list, click Show, and then type the module names in the list. The modules in the list must be installed on the computer. + + Note: This policy setting exists under both Computer Configuration and User Configuration in the Group Policy Editor. The Computer Configuration policy setting takes precedence over the User Configuration policy setting. + + + Turn on PowerShell Transcription + + This policy setting lets you capture the input and output of PowerShell Core commands into text-based transcripts. + + If you enable this policy setting, PowerShell Core will enable transcription logging for PowerShell Core and any other + applications that leverage the PowerShell Core engine. By default, PowerShell Core will record transcript output to each users' My Documents + directory, with a file name that includes 'PowerShell_transcript', along with the computer name and time started. Enabling this policy is equivalent + to calling the Start-Transcript cmdlet on each PowerShell Core session. + + If you disable this policy setting, transcription logging of PowerShell-based applications is disabled by default, although transcripting can still be enabled + through the Start-Transcript cmdlet. + + If you use the OutputDirectory setting to enable transcription logging to a shared location, be sure to limit access to that directory to prevent users + from viewing the transcripts of other users or computers. + + Note: This policy setting exists under both Computer Configuration and User Configuration in the Group Policy Editor. The Computer Configuration policy setting takes precedence over the User Configuration policy setting. + + + Turn on PowerShell Script Block Logging + + This policy setting enables logging of all PowerShell script input to the Microsoft-Windows-PowerShell/Operational event log. If you enable this policy setting, + PowerShell Core will log the processing of commands, script blocks, functions, and scripts - whether invoked interactively, or through automation. + + If you disable this policy setting, logging of PowerShell script input is disabled. + + If you enable the Script Block Invocation Logging, PowerShell additionally logs events when invocation of a command, script block, function, or script + starts or stops. Enabling Invocation Logging generates a high volume of event logs. + + Note: This policy setting exists under both Computer Configuration and User Configuration in the Group Policy Editor. The Computer Configuration policy setting takes precedence over the User Configuration policy setting. + + + Set the default source path for Update-Help + This policy setting allows you to set the default value of the SourcePath parameter on the Update-Help cmdlet. + +If you enable this policy setting, the Update-Help cmdlet will use the specified value as the default value for the SourcePath parameter. This default value can be overridden by specifying a different value with the SourcePath parameter on the Update-Help cmdlet. + +If this policy setting is disabled or not configured, this policy setting does not set a default value for the SourcePath parameter of the Update-Help cmdlet. + +Note: This policy setting exists under both Computer Configuration and User Configuration in the Group Policy Editor. The Computer Configuration policy setting takes precedence over the User Configuration policy setting. + + Console session configuration + Specifies a configuration endpoint in which PowerShell is run. This can be any endpoint registered on the local machine including the default PowerShell remoting endpoints or a custom endpoint having specific user role capabilities. + + + + + + Use Windows PowerShell Policy setting. + Execution Policy + + + Use Windows PowerShell Policy setting. + To turn on logging for one or more modules, click Show, and then type the module names in the list. Wildcards are supported. + Module Names + To turn on logging for the PowerShell Core core modules, type the following module names in the list: + Microsoft.PowerShell.* + Microsoft.WSMan.Management + + + Use Windows PowerShell Policy setting. + + Include invocation headers: + + + Use Windows PowerShell Policy setting. + Log script block invocation start / stop events: + + + Use Windows PowerShell Policy setting. + + + + + + + + + + + + + diff --git a/assets/GroupPolicy/PowerShellCoreExecutionPolicy.admx b/assets/GroupPolicy/PowerShellCoreExecutionPolicy.admx new file mode 100644 index 00000000000..55e888fdfe9 --- /dev/null +++ b/assets/GroupPolicy/PowerShellCoreExecutionPolicy.admx @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AllSigned + + + + + RemoteSigned + + + + + Unrestricted + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/Product.wxs b/assets/Product.wxs index f0fc38f0f51..82ee672072b 100644 --- a/assets/Product.wxs +++ b/assets/Product.wxs @@ -126,10 +126,15 @@ + + + + + @@ -157,16 +162,19 @@ + + + - - - - - - - - + + + + + + + + diff --git a/assets/files.wxs b/assets/files.wxs index 78984cd748a..eec4e1ec04e 100644 --- a/assets/files.wxs +++ b/assets/files.wxs @@ -205,9 +205,6 @@ - - - @@ -1699,6 +1696,14 @@ + + + + + + + + @@ -3048,8 +3053,17 @@ - - + + + + + + + + + + + @@ -3123,7 +3137,6 @@ - @@ -3887,7 +3900,12 @@ - + + + + + + diff --git a/build.psm1 b/build.psm1 index 9169d1cbdad..0e0cf31ed5d 100644 --- a/build.psm1 +++ b/build.psm1 @@ -446,12 +446,6 @@ Fix steps: Pop-Location } - # publish powershell.config.json - $config = @{} - if ($Environment.IsWindows) { - $config = @{ "Microsoft.PowerShell:ExecutionPolicy" = "RemoteSigned" } - } - if ($ReleaseTag) { $psVersion = $ReleaseTag } @@ -459,18 +453,6 @@ Fix steps: $psVersion = git --git-dir="$PSSCriptRoot/.git" describe } - # ARM is cross compiled, so we can't run pwsh to enumerate Experimental Features - if ((Test-IsPreview $psVersion) -and -not $Runtime.Contains("arm")) { - $expFeatures = [System.Collections.Generic.List[string]]::new() - & $publishPath\pwsh -noprofile -out XML -command Get-ExperimentalFeature | ForEach-Object { $expFeatures.Add($_.Name) } - $config += @{ ExperimentalFeatures = $expFeatures.ToArray() } - } - - if ($config.Count -gt 0) { - $configPublishPath = Join-Path -Path $publishPath -ChildPath "powershell.config.json" - Set-Content -Path $configPublishPath -Value ($config | ConvertTo-Json) -Force -ErrorAction Stop - } - if ($Environment.IsRedHatFamily -or $Environment.IsDebian9) { # add two symbolic links to system shared libraries that libmi.so is dependent on to handle # platform specific changes. This is the only set of platforms needed for this currently @@ -501,6 +483,37 @@ Fix steps: Restore-PSModuleToBuild -PublishPath $publishPath } + # publish powershell.config.json + $config = @{} + if ($Environment.IsWindows) { + $config = @{ "Microsoft.PowerShell:ExecutionPolicy" = "RemoteSigned" } + } + + # ARM is cross compiled, so we can't run pwsh to enumerate Experimental Features + if ((Test-IsPreview $psVersion) -and -not $Runtime.Contains("arm") -and -not ($Runtime -like 'fxdependent*')) { + $json = & $publishPath\pwsh -noprofile -command { + $expFeatures = [System.Collections.Generic.List[string]]::new() + Get-ExperimentalFeature | ForEach-Object { $expFeatures.Add($_.Name) } + + # Make sure ExperimentalFeatures from modules in PSHome are added + # https://github.com/PowerShell/PowerShell/issues/10550 + @("PSDesiredStateConfiguration.InvokeDscResource") | ForEach-Object { + if (!$expFeatures.Contains($_)) { + $expFeatures.Add($_) + } + } + + ConvertTo-Json $expFeatures.ToArray() + } + + $config += @{ ExperimentalFeatures = ([string[]] ($json | ConvertFrom-Json)) } + } + + if ($config.Count -gt 0) { + $configPublishPath = Join-Path -Path $publishPath -ChildPath "powershell.config.json" + Set-Content -Path $configPublishPath -Value ($config | ConvertTo-Json) -Force -ErrorAction Stop + } + # Restore the Pester module if ($CI) { Restore-PSPester -Destination (Join-Path $publishPath "Modules") @@ -1006,7 +1019,13 @@ function Start-PSPester { Write-Verbose "Running pester tests at '$path' with tag '$($Tag -join ''', ''')' and ExcludeTag '$($ExcludeTag -join ''', ''')'" -Verbose if(!$SkipTestToolBuild.IsPresent) { - Publish-PSTestTools | ForEach-Object {Write-Host $_} + $publishArgs = @{ } + # if we are building for Alpine, we must include the runtime as linux-x64 + # will not build runnable test tools + if ( $Environment.IsAlpine ) { + $publishArgs['runtime'] = 'alpine-x64' + } + Publish-PSTestTools @publishArgs | ForEach-Object {Write-Host $_} } # All concatenated commands/arguments are suffixed with the delimiter (space) diff --git a/docs/learning-powershell/README.md b/docs/learning-powershell/README.md index ba70b54f99d..1e57e56363b 100644 --- a/docs/learning-powershell/README.md +++ b/docs/learning-powershell/README.md @@ -109,13 +109,15 @@ Note that all bash commands should continue working on PowerShell session. - [Windows PowerShell in Action][in-action] by [Bruce Payette](https://github.com/brucepay) - [Introduction to PowerShell][powershell-intro] from Pluralsight - [PowerShell Training and Tutorials][lynda-training] from Lynda.com -- [Learn Windows PowerShell in a Month of Lunches][learn-powershell] by Don Jones and Jeffrey Hicks - +- [Learn Windows PowerShell in a Month of Lunches][learn-win-powershell] by Don Jones and Jeffrey Hicks +- [Learn PowerShell in a Month of Lunches][learn-powershell] by Travis Plunk (@TravisEz13), + Tyler Leonhardt (@tylerleonhardt), Don Jones, and Jeffery Hicks [in-action]: https://www.amazon.com/Windows-PowerShell-Action-Second-Payette/dp/1935182137 [powershell-intro]: https://www.pluralsight.com/courses/powershell-intro [lynda-training]: https://www.lynda.com/PowerShell-training-tutorials/5779-0.html -[learn-powershell]: https://www.amazon.com/Learn-Windows-PowerShell-Month-Lunches/dp/1617294160 +[learn-win-powershell]: https://www.amazon.com/Learn-Windows-PowerShell-Month-Lunches/dp/1617294160 +[learn-powershell]: https://www.manning.com/books/learn-powershell-in-a-month-of-lunches-linux-and-macos-edition [getstarted-with-powershell]: https://channel9.msdn.com/Series/GetStartedPowerShell3 [why-learn-powershell]: https://blogs.technet.microsoft.com/heyscriptingguy/2014/10/18/weekend-scripter-why-learn-powershell/ diff --git a/global.json b/global.json index 4201a8ffdf7..79422f0cc1c 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "3.0.100-preview8-013656" + "version": "3.0.100" } } diff --git a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj index 1d851a24af4..af023882fb5 100644 --- a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj +++ b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj @@ -57,7 +57,7 @@ - + diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetContentCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetContentCommand.cs index 16a06ab10f8..bc1e87042c7 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetContentCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetContentCommand.cs @@ -338,13 +338,9 @@ private bool ScanForwardsForTail(ContentHolder holder, CmdletProviderContext cur if (ReadCount <= 0 || (ReadCount >= tailResultQueue.Count && ReadCount != 1)) { count = tailResultQueue.Count; - ArrayList outputList = new ArrayList(); - while (tailResultQueue.Count > 0) - { - outputList.Add(tailResultQueue.Dequeue()); - } + // Write out the content as an array of objects - WriteContentObject(outputList.ToArray(), count, holder.PathInfo, currentContext); + WriteContentObject(tailResultQueue.ToArray(), count, holder.PathInfo, currentContext); } else if (ReadCount == 1) { @@ -356,7 +352,7 @@ private bool ScanForwardsForTail(ContentHolder holder, CmdletProviderContext cur { while (tailResultQueue.Count >= ReadCount) { - ArrayList outputList = new ArrayList(); + var outputList = new List((int)ReadCount); for (int idx = 0; idx < ReadCount; idx++, count++) outputList.Add(tailResultQueue.Dequeue()); // Write out the content as an array of objects @@ -366,11 +362,8 @@ private bool ScanForwardsForTail(ContentHolder holder, CmdletProviderContext cur int remainder = tailResultQueue.Count; if (remainder > 0) { - ArrayList outputList = new ArrayList(); - for (; remainder > 0; remainder--, count++) - outputList.Add(tailResultQueue.Dequeue()); // Write out the content as an array of objects - WriteContentObject(outputList.ToArray(), count, holder.PathInfo, currentContext); + WriteContentObject(tailResultQueue.ToArray(), count, holder.PathInfo, currentContext); } } } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index 9f27b746a2f..e5b7887620c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -2274,11 +2274,8 @@ protected override void BeginProcessing() } // write the ServiceController for the new service - using (ServiceController service = - new ServiceController(Name)) // ensure dispose - { - WriteObject(service); - } + ServiceController service = new ServiceController(Name); + WriteObject(service); } finally { @@ -2764,7 +2761,7 @@ byte[] lpSecurityDescriptor /// If the object existed before the function call, the function /// returns a handle to the existing job object. /// - [DllImport(PinvokeDllNames.CreateJobObjectDllName, CharSet = CharSet.Unicode)] + [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)] internal static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string lpName); /// @@ -2779,7 +2776,7 @@ byte[] lpSecurityDescriptor /// If the function succeeds, the return value is nonzero. /// If the function fails, the return value is zero. /// - [DllImport(PinvokeDllNames.AssignProcessToJobObjectDllName, CharSet = CharSet.Unicode)] + [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool AssignProcessToJobObject(SafeHandle hJob, IntPtr hProcess); @@ -2805,7 +2802,7 @@ byte[] lpSecurityDescriptor /// If the function succeeds, the return value is nonzero. /// If the function fails, the return value is zero. /// - [DllImport(PinvokeDllNames.QueryInformationJobObjectDllName, EntryPoint = "QueryInformationJobObject", SetLastError = true, CharSet = CharSet.Unicode)] + [DllImport("Kernel32.dll", EntryPoint = "QueryInformationJobObject", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool QueryInformationJobObject(SafeHandle hJob, int JobObjectInfoClass, ref JOBOBJECT_BASIC_PROCESS_ID_LIST lpJobObjectInfo, int cbJobObjectLength, IntPtr lpReturnLength); diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 0a8f60f82a0..a8375e54b49 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -17,22 +17,21 @@ namespace Microsoft.PowerShell.Commands /// /// The implementation of the "Test-Connection" cmdlet. /// - [Cmdlet(VerbsDiagnostic.Test, "Connection", DefaultParameterSetName = DefaultPingSet, + [Cmdlet(VerbsDiagnostic.Test, "Connection", DefaultParameterSetName = DefaultPingParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135266")] - [OutputType(typeof(PingStatus), ParameterSetName = new[] { DefaultPingSet })] - [OutputType(typeof(PingReply), ParameterSetName = new[] { RepeatPingSet, MtuSizeDetectSet })] - [OutputType(typeof(bool), ParameterSetName = new[] { DefaultPingSet, RepeatPingSet, TcpPortSet })] - [OutputType(typeof(int), ParameterSetName = new[] { MtuSizeDetectSet })] - [OutputType(typeof(TraceStatus), ParameterSetName = new[] { TraceRouteSet })] + [OutputType(typeof(PingStatus), ParameterSetName = new string[] { DefaultPingParameterSet })] + [OutputType(typeof(PingReply), ParameterSetName = new string[] { RepeatPingParameterSet, MtuSizeDetectParameterSet })] + [OutputType(typeof(bool), ParameterSetName = new string[] { DefaultPingParameterSet, RepeatPingParameterSet, TcpPortParameterSet })] + [OutputType(typeof(int), ParameterSetName = new string[] { MtuSizeDetectParameterSet })] + [OutputType(typeof(TraceStatus), ParameterSetName = new string[] { TraceRouteParameterSet })] public class TestConnectionCommand : PSCmdlet, IDisposable { - #region ParameterSetName Constants - - private const string DefaultPingSet = "DefaultPing"; - private const string RepeatPingSet = "RepeatPing"; - private const string TraceRouteSet = "TraceRoute"; - private const string TcpPortSet = "TcpPort"; - private const string MtuSizeDetectSet = "MtuSizeDetect"; + #region Parameter Set Names + private const string DefaultPingParameterSet = "DefaultPing"; + private const string RepeatPingParameterSet = "RepeatPing"; + private const string TraceRouteParameterSet = "TraceRoute"; + private const string TcpPortParameterSet = "TcpPort"; + private const string MtuSizeDetectParameterSet = "MtuSizeDetect"; #endregion @@ -53,7 +52,7 @@ public class TestConnectionCommand : PSCmdlet, IDisposable private static byte[] s_DefaultSendBuffer = null; - private bool disposed = false; + private bool _disposed = false; private readonly Ping _sender = new Ping(); @@ -69,26 +68,38 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// Gets or sets whether to do ping test. /// Default is true. /// - [Parameter(ParameterSetName = DefaultPingSet)] - [Parameter(ParameterSetName = RepeatPingSet)] + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] public SwitchParameter Ping { get; set; } = true; /// /// Gets or sets whether to force use of IPv4 protocol. /// - [Parameter] + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] + [Parameter(ParameterSetName = TraceRouteParameterSet)] + [Parameter(ParameterSetName = MtuSizeDetectParameterSet)] + [Parameter(ParameterSetName = TcpPortParameterSet)] public SwitchParameter IPv4 { get; set; } /// /// Gets or sets whether to force use of IPv6 protocol. /// - [Parameter] + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] + [Parameter(ParameterSetName = TraceRouteParameterSet)] + [Parameter(ParameterSetName = MtuSizeDetectParameterSet)] + [Parameter(ParameterSetName = TcpPortParameterSet)] public SwitchParameter IPv6 { get; set; } /// /// Gets or sets whether to do reverse DNS lookup to get names for IP addresses. /// - [Parameter] + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] + [Parameter(ParameterSetName = TraceRouteParameterSet)] + [Parameter(ParameterSetName = MtuSizeDetectParameterSet)] + [Parameter(ParameterSetName = TcpPortParameterSet)] public SwitchParameter ResolveDestination { get; set; } /// @@ -96,10 +107,10 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// The default is localhost. /// Remoting is not yet implemented internally in the cmdlet. /// - [Parameter(ParameterSetName = DefaultPingSet)] - [Parameter(ParameterSetName = RepeatPingSet)] - [Parameter(ParameterSetName = TraceRouteSet)] - [Parameter(ParameterSetName = TcpPortSet)] + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] + [Parameter(ParameterSetName = TraceRouteParameterSet)] + [Parameter(ParameterSetName = TcpPortParameterSet)] public string Source { get; } = Dns.GetHostName(); /// @@ -108,9 +119,9 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// value found in the packet header. /// The default (from Windows) is 128 hops. /// - [Parameter(ParameterSetName = DefaultPingSet)] - [Parameter(ParameterSetName = RepeatPingSet)] - [Parameter(ParameterSetName = TraceRouteSet)] + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] + [Parameter(ParameterSetName = TraceRouteParameterSet)] [ValidateRange(0, sMaxHops)] [Alias("Ttl", "TimeToLive", "Hops")] public int MaxHops { get; set; } = sMaxHops; @@ -121,7 +132,7 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// Gets or sets the number of ping attempts. /// The default (from Windows) is 4 times. /// - [Parameter(ParameterSetName = DefaultPingSet)] + [Parameter(ParameterSetName = DefaultPingParameterSet)] [ValidateRange(ValidateRangeKind.Positive)] public int Count { get; set; } = 4; @@ -129,8 +140,8 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// Gets or sets the delay between ping attempts. /// The default (from Windows) is 1 second. /// - [Parameter(ParameterSetName = DefaultPingSet)] - [Parameter(ParameterSetName = RepeatPingSet)] + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] [ValidateRange(ValidateRangeKind.Positive)] public int Delay { get; set; } = 1; @@ -139,8 +150,8 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// The default (from Windows) is 32 bytes. /// Max value is 65500 (limitation imposed by Windows API). /// - [Parameter(ParameterSetName = DefaultPingSet)] - [Parameter(ParameterSetName = RepeatPingSet)] + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] [Alias("Size", "Bytes", "BS")] [ValidateRange(0, 65500)] public int BufferSize { get; set; } = DefaultSendBufferSize; @@ -149,14 +160,14 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// Gets or sets whether to prevent fragmentation of the ICMP packets. /// Currently CoreFX not supports this on Unix. /// - [Parameter(ParameterSetName = DefaultPingSet)] - [Parameter(ParameterSetName = RepeatPingSet)] + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] public SwitchParameter DontFragment { get; set; } /// /// Gets or sets whether to continue pinging until user presses Ctrl-C (or Int.MaxValue threshold reached). /// - [Parameter(Mandatory = true, ParameterSetName = RepeatPingSet)] + [Parameter(Mandatory = true, ParameterSetName = RepeatPingParameterSet)] [Alias("Continues", "Continuous")] public SwitchParameter Repeat { get; set; } @@ -194,21 +205,21 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// When selected, only a single ping result is returned, indicating the maximum buffer size /// the route to the destination can support without fragmenting the ICMP packets. /// - [Parameter(Mandatory = true, ParameterSetName = MtuSizeDetectSet)] + [Parameter(Mandatory = true, ParameterSetName = MtuSizeDetectParameterSet)] [Alias("MtuSizeDetect")] public SwitchParameter MtuSize { get; set; } /// /// Gets or sets whether to perform a traceroute test. /// - [Parameter(Mandatory = true, ParameterSetName = TraceRouteSet)] + [Parameter(Mandatory = true, ParameterSetName = TraceRouteParameterSet)] public SwitchParameter Traceroute { get; set; } /// /// Gets or sets whether to perform a TCP connection test. /// [ValidateRange(0, 65535)] - [Parameter(Mandatory = true, ParameterSetName = TcpPortSet)] + [Parameter(Mandatory = true, ParameterSetName = TcpPortParameterSet)] public int TcpPort { get; set; } #endregion Parameters @@ -223,7 +234,7 @@ protected override void BeginProcessing() switch (ParameterSetName) { - case RepeatPingSet: + case RepeatPingParameterSet: Count = int.MaxValue; break; } @@ -238,17 +249,17 @@ protected override void ProcessRecord() { switch (ParameterSetName) { - case DefaultPingSet: - case RepeatPingSet: + case DefaultPingParameterSet: + case RepeatPingParameterSet: ProcessPing(targetName); break; - case MtuSizeDetectSet: + case MtuSizeDetectParameterSet: ProcessMTUSize(targetName); break; - case TraceRouteSet: + case TraceRouteParameterSet: ProcessTraceroute(targetName); break; - case TcpPortSet: + case TcpPortParameterSet: ProcessConnectionByTCPPort(targetName); break; } @@ -273,8 +284,6 @@ private void ProcessConnectionByTCPPort(string targetNameOrAddress) return; } - WriteVerboseConnectionTestHeader(resolvedTargetName, targetAddress.ToString()); - TcpClient client = new TcpClient(); try @@ -284,8 +293,6 @@ private void ProcessConnectionByTCPPort(string targetNameOrAddress) for (var i = 1; i <= TimeoutSeconds; i++) { - WriteVerboseConnectionTest(targetNameOrAddress, targetString, i); - Task timeoutTask = Task.Delay(millisecondsDelay: 1000); Task.WhenAny(connectionTask, timeoutTask).Result.Wait(); @@ -314,24 +321,6 @@ private void ProcessConnectionByTCPPort(string targetNameOrAddress) WriteObject(false); } - - private void WriteVerboseConnectionTestHeader(string resolvedTargetName, string targetAddress) - { - WriteVerbose(StringUtil.Format( - TestConnectionResources.ConnectionTestStart, - resolvedTargetName, - targetAddress)); - } - - private void WriteVerboseConnectionTest(string targetNameOrAddress, string targetAddress, int timeout) - { - WriteVerbose(StringUtil.Format( - TestConnectionResources.ConnectionTestDescription, - targetNameOrAddress, - targetAddress, - timeout)); - } - #endregion ConnectionTest #region TracerouteTest @@ -344,12 +333,6 @@ private void ProcessTraceroute(string targetNameOrAddress) return; } - WriteVerbose(StringUtil.Format( - TestConnectionResources.TraceRouteStart, - resolvedTargetName, - targetAddress.ToString(), - MaxHops)); - int currentHop = 1; PingOptions pingOptions = new PingOptions(currentHop, DontFragment.IsPresent); PingReply reply = null; @@ -363,8 +346,6 @@ private void ProcessTraceroute(string targetNameOrAddress) pingOptions.Ttl = currentHop; currentHop++; - var hopReplies = new List(DefaultTraceRoutePingCount); - // In traceroutes we don't use 'Count' parameter. // If we change 'DefaultTraceRoutePingCount' we should change 'ConsoleTraceRouteReply' resource string. for (uint i = 1; i <= DefaultTraceRoutePingCount; i++) @@ -401,7 +382,9 @@ private void ProcessTraceroute(string targetNameOrAddress) routerName, reply, pingOptions, - latency: reply.Status == IPStatus.Success ? reply.RoundtripTime : timer.ElapsedMilliseconds, + latency: reply.Status == IPStatus.Success + ? reply.RoundtripTime + : timer.ElapsedMilliseconds, buffer.Length, pingNum: i); hopResult = new TraceStatus(currentHop, status, Source, resolvedTargetName, targetAddress); @@ -430,56 +413,19 @@ private void ProcessTraceroute(string targetNameOrAddress) continue; } - hopReplies.Add(hopResult); - - // We use a short delay because it is impossible to DoS with traceroute. + // We use short delay because it is impossible DoS with trace route. Thread.Sleep(50); } - - WriteVerboseTraceHop(hopReplies); - } while (reply != null && currentHop <= sMaxHops && (reply.Status == IPStatus.TtlExpired || reply.Status == IPStatus.TimedOut)); - WriteVerbose(TestConnectionResources.TraceRouteComplete); - if (Quiet.IsPresent) { WriteObject(currentHop <= sMaxHops); } } - private void WriteVerboseTraceHop(IList traceRouteReplies) - { - string msg; - if (traceRouteReplies[2].Status == IPStatus.Success) - { - var routerAddress = traceRouteReplies[2].HopAddress.ToString(); - var routerName = traceRouteReplies[2].Hostname ?? routerAddress; - var roundtripTime0 = traceRouteReplies[0].Status == IPStatus.TimedOut - ? "*" - : traceRouteReplies[0].Latency.ToString(); - var roundtripTime1 = traceRouteReplies[1].Status == IPStatus.TimedOut - ? "*" - : traceRouteReplies[1].Latency.ToString(); - msg = StringUtil.Format( - TestConnectionResources.TraceRouteReply, - traceRouteReplies[0].Hop, - roundtripTime0, - roundtripTime1, - traceRouteReplies[2].Latency.ToString(), - routerName, - routerAddress); - } - else - { - msg = StringUtil.Format(TestConnectionResources.TraceRouteTimeOut, traceRouteReplies[0].Hop); - } - - WriteVerbose(msg); - } - #endregion TracerouteTest #region MTUSizeTest @@ -512,11 +458,6 @@ private void ProcessMTUSize(string targetNameOrAddress) { byte[] buffer = GetSendBuffer(CurrentMTUSize); - WriteVerbose(StringUtil.Format( - TestConnectionResources.MTUSizeDetectDescription, - CurrentMTUSize, - retry)); - WriteDebug(StringUtil.Format( "LowMTUSize: {0}, CurrentMTUSize: {1}, HighMTUSize: {2}", LowMTUSize, @@ -601,15 +542,6 @@ private void ProcessPing(string targetNameOrAddress) return; } - if (!Repeat.IsPresent) - { - WriteVerbose(StringUtil.Format( - TestConnectionResources.MTUSizeDetectStart, - resolvedTargetName, - targetAddress.ToString(), - BufferSize)); - } - bool quietResult = true; byte[] buffer = GetSendBuffer(BufferSize); @@ -643,19 +575,14 @@ private void ProcessPing(string targetNameOrAddress) { WriteObject(reply); } + else if (Quiet.IsPresent) + { + // Return 'true' only if all pings have completed successfully. + quietResult &= reply.Status == IPStatus.Success; + } else { - if (Quiet.IsPresent) - { - // Return 'true' only if all pings have completed successfully. - quietResult &= reply.Status == IPStatus.Success; - } - else - { - WriteObject(new PingStatus(Source, resolvedTargetName, reply, i)); - } - - WriteVerbosePing(reply); + WriteObject(new PingStatus(Source, resolvedTargetName, reply, i)); } // Delay between pings, but not after last ping. @@ -665,34 +592,12 @@ private void ProcessPing(string targetNameOrAddress) } } - if (!Repeat.IsPresent) - { - WriteVerbose(TestConnectionResources.PingComplete); - } - if (Quiet.IsPresent) { WriteObject(quietResult); } } - private void WriteVerbosePing(PingReply reply) - { - if (reply.Status != IPStatus.Success) - { - WriteVerbose(TestConnectionResources.PingTimeOut); - } - else - { - WriteVerbose(StringUtil.Format( - TestConnectionResources.PingReply, - reply.Address.ToString(), - reply.Buffer.Length, - reply.RoundtripTime, - reply.Options?.Ttl)); - } - } - #endregion PingTest private bool InitProcessPing(string targetNameOrAddress, out string resolvedTargetName, out IPAddress targetAddress) @@ -815,7 +720,7 @@ public void Dispose() /// protected virtual void Dispose(bool disposing) { - if (!disposed) + if (!_disposed) { if (disposing) { @@ -823,7 +728,7 @@ protected virtual void Dispose(bool disposing) _pingComplete?.Dispose(); } - disposed = true; + _disposed = true; } } @@ -841,7 +746,7 @@ private PingReply SendCancellablePing( timer?.Stop(); // Pause to let _sender's async flags to be reset properly so the next SendAsync call doesn't fail. - Thread.Sleep(1); + Thread.Sleep(2); if (_pingCompleteArgs.Cancelled) { @@ -1085,7 +990,7 @@ public IPStatus Status } /// - /// Finalizer for IDisposable class. + /// Finalizes an instance of the class. /// ~TestConnectionCommand() { diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs index dc1221f81ee..68560bef20f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs @@ -333,7 +333,7 @@ private string[] GetAcceptedPaths(string[] unfilteredPaths, CmdletProviderContex { Collection pathInfos = ResolvePaths(unfilteredPaths, true, false, currentContext); - ArrayList paths = new ArrayList(); + var paths = new List(); foreach (PathInfo pathInfo in pathInfos) { @@ -343,7 +343,7 @@ private string[] GetAcceptedPaths(string[] unfilteredPaths, CmdletProviderContex } } - return (string[])paths.ToArray(typeof(string)); + return paths.ToArray(); } #endregion protected members diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/TestConnectionResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/TestConnectionResources.resx index 794680166cc..08dcf439e28 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/TestConnectionResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/TestConnectionResources.resx @@ -117,30 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Tracing route to {0} [{1}] over a maximum of {2} hops: - - - {0,3} {1} ms {2} ms {3} ms {4} [{5}] - - - {0,3} * ms * ms * ms Request timed out. - - - Trace complete. - - - Trying to connect to {0} [{1}]: - - - Target: {0} [{1}]. Seconds: {2} - - - Pinging {0} [{1}] with {2} bytes of data: - - - MTU size: {0}. Attempt: {1} - Testing connection to computer '{0}' failed: {1} @@ -150,16 +126,4 @@ Target IPv4/IPv6 address absent. - - Pinging {0} [{1}] with {2} bytes of data: - - - Request timed out. - - - Reply from {0}: bytes={1} time={2}ms TTL={3} - - - Ping complete. - diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index 6ceadabe013..0b83959a440 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -55,9 +55,9 @@ - - - + + + diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs index 4e6fc676432..0f795cb2a23 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs @@ -596,10 +596,24 @@ protected override void EndProcessing() #region LoadAssembly - // We now ship .Net Core's reference assemblies with PowerShell, so that Add-Type can work + // We now ship .NET Core's reference assemblies with PowerShell, so that Add-Type can work // in a predictable way and won't be broken when we move to newer version of .NET Core. - // The reference assemblies are located at '$PSHOME\ref'. - private static readonly string s_netcoreAppRefFolder = PathType.Combine(PathType.GetDirectoryName(typeof(PSObject).Assembly.Location), "ref"); + // The reference assemblies are located at '$PSHOME\ref' for pwsh. + // + // For applications that host PowerShell, the 'ref' folder will be deployed to the 'publish' + // folder, not where 'System.Management.Automation.dll' is located. So here we should use + // the entry assembly's location to construct the path to the 'ref' folder. + // For pwsh, the entry assembly is 'pwsh.dll', so the entry assembly's location is still + // $PSHOME. + // However, 'Assembly.GetEntryAssembly()' returns null when the managed code is called from + // unmanaged code (PowerShell WSMan remoting scenario), so in that case, we continue to use + // the location of 'System.Management.Automation.dll'. + private static readonly string s_netcoreAppRefFolder = PathType.Combine( + PathType.GetDirectoryName( + (Assembly.GetEntryAssembly() ?? typeof(PSObject).Assembly).Location), + "ref"); + + // Path to the folder where .NET Core runtime assemblies are located. private static readonly string s_frameworkFolder = PathType.GetDirectoryName(typeof(object).Assembly.Location); // These assemblies are always automatically added to ReferencedAssemblies. diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertTo-Html.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertTo-Html.cs index 1374ce6aa45..432dd5ce5b1 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertTo-Html.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertTo-Html.cs @@ -359,7 +359,7 @@ private List ProcessParameter(object[] properties) private void InitializeResolvedNameMshParameters() { // temp list of properties with wildcards resolved - ArrayList resolvedNameProperty = new ArrayList(); + var resolvedNameProperty = new List(); foreach (MshParameter p in _propertyMshParameterList) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs index 02e6fcad671..7b22cbcddd9 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs @@ -101,19 +101,11 @@ public Guid InstanceId } /// - /// The optional breakpoint objects to use for debugging. + /// Gets or sets a flag that tells PowerShell to automatically perform a BreakAll when the debugger is attached to the remote target. /// - [Experimental("Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints", ExperimentAction.Show)] - [Parameter(Position = 1, - ParameterSetName = DebugRunspaceCommand.InstanceIdParameterSet)] - [Parameter(ParameterSetName = DebugRunspaceCommand.RunspaceParameterSet)] - [Parameter(ParameterSetName = DebugRunspaceCommand.IdParameterSet)] - [Parameter(ParameterSetName = DebugRunspaceCommand.NameParameterSet)] - public Breakpoint[] Breakpoint - { - get; - set; - } + [Experimental("Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace", ExperimentAction.Show)] + [Parameter] + public SwitchParameter BreakAll { get; set; } #endregion @@ -275,7 +267,7 @@ private void WaitAndReceiveRunspaceOutput() _debugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript); // Set up host script debugger to debug the runspace. - _debugger.DebugRunspace(_runspace, disableBreakAll: Breakpoint?.Length > 0); + _debugger.DebugRunspace(_runspace, breakAll: BreakAll); while (_debugging) { @@ -532,10 +524,6 @@ private void PrepareRunspace(Runspace runspace) { SetLocalMode(runspace.Debugger, true); EnableHostDebugger(runspace, false); - if (Breakpoint?.Length > 0) - { - runspace.Debugger?.SetBreakpoints(Breakpoint); - } } private void RestoreRunspace(Runspace runspace) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs index a9192ae2da9..02869619e3f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs @@ -350,22 +350,6 @@ public SwitchParameter BreakAll set; } - /// - /// Gets or sets the optional breakpoint objects to use for debugging. - /// - [Experimental("Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints", ExperimentAction.Show)] - [Parameter(Position = 1, - ParameterSetName = CommonRunspaceCommandBase.RunspaceParameterSet)] - [Parameter(Position = 1, - ParameterSetName = CommonRunspaceCommandBase.RunspaceNameParameterSet)] - [Parameter(Position = 1, - ParameterSetName = CommonRunspaceCommandBase.RunspaceIdParameterSet)] - public Breakpoint[] Breakpoint - { - get; - set; - } - #endregion #region Overrides @@ -428,12 +412,6 @@ protected override void ProcessRecord() debugger.SetDebuggerStepMode(false); } } - - // If any breakpoints were provided, set those in the debugger. - if (Breakpoint?.Length > 0) - { - debugger.SetBreakpoints(Breakpoint); - } } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommand.cs index 1a7c4b1a8d6..f621dd3c7b3 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommand.cs @@ -28,6 +28,7 @@ public class GetRandomCommand : PSCmdlet private const string RandomNumberParameterSet = "RandomNumberParameterSet"; private const string RandomListItemParameterSet = "RandomListItemParameterSet"; + private static readonly object[] _nullInArray = new object[] { null }; private enum MyParameterSet { @@ -275,7 +276,7 @@ private double ConvertToDouble(object o, double defaultIfNull) /// List from which random elements are chosen. /// [Parameter(ParameterSetName = RandomListItemParameterSet, ValueFromPipeline = true, Position = 0, Mandatory = true)] - [ValidateNotNullOrEmpty] + [System.Management.Automation.AllowNull] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public object[] InputObject { get; set; } @@ -491,9 +492,11 @@ protected override void ProcessRecord() { if (EffectiveParameterSet == MyParameterSet.RandomListItem) { - foreach (object item in InputObject) + // this allows for $null to be in an array passed to InputObject + foreach (object item in InputObject ?? _nullInArray) { - if (_numberOfProcessedListItems < Count) // (3) + // (3) + if (_numberOfProcessedListItems < Count) { Debug.Assert(_chosenListItems.Count == _numberOfProcessedListItems, "Initial K elements should all be included in chosenListItems"); _chosenListItems.Add(item); @@ -501,9 +504,12 @@ protected override void ProcessRecord() else { Debug.Assert(_chosenListItems.Count == Count, "After processing K initial elements, the length of chosenItems should stay equal to K"); - if (Generator.Next(_numberOfProcessedListItems + 1) < Count) // (1) + + // (1) + if (Generator.Next(_numberOfProcessedListItems + 1) < Count) { - int indexToReplace = Generator.Next(_chosenListItems.Count); // (2) + // (2) + int indexToReplace = Generator.Next(_chosenListItems.Count); _chosenListItems[indexToReplace] = item; } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs deleted file mode 100644 index b5837cc0da3..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.IO; -using System.Management.Automation; -using System.Management.Automation.Internal; - -namespace Microsoft.PowerShell.Commands -{ - /// - /// This class implements New-PSBreakpoint command. - /// - [Experimental("Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints", ExperimentAction.Show)] - [Cmdlet(VerbsCommon.New, "PSBreakpoint", DefaultParameterSetName = LineParameterSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113449")] - [OutputType(typeof(VariableBreakpoint), typeof(CommandBreakpoint), typeof(LineBreakpoint))] - public class NewPSBreakpointCommand : PSBreakpointCreationBase - { - /// - /// Create a new breakpoint. - /// - protected override void ProcessRecord() - { - // If there is a script, resolve its path - Collection scripts = ResolveScriptPaths(); - - // If it is a command breakpoint... - if (ParameterSetName.Equals(CommandParameterSetName, StringComparison.OrdinalIgnoreCase)) - { - for (int i = 0; i < Command.Length; i++) - { - if (scripts.Count > 0) - { - foreach (string path in scripts) - { - WildcardPattern pattern = WildcardPattern.Get(Command[i], WildcardOptions.Compiled | WildcardOptions.IgnoreCase); - WriteObject(new CommandBreakpoint(path, pattern, Command[i], Action)); - } - } - else - { - WildcardPattern pattern = WildcardPattern.Get(Command[i], WildcardOptions.Compiled | WildcardOptions.IgnoreCase); - WriteObject(new CommandBreakpoint(null, pattern, Command[i], Action)); - } - } - } - else if (ParameterSetName.Equals(VariableParameterSetName, StringComparison.OrdinalIgnoreCase)) - { - // If it is a variable breakpoint... - for (int i = 0; i < Variable.Length; i++) - { - if (scripts.Count > 0) - { - foreach (string path in scripts) - { - WriteObject(new VariableBreakpoint(path, Variable[i], Mode, Action)); - } - } - else - { - WriteObject(new VariableBreakpoint(null, Variable[i], Mode, Action)); - } - } - } - else - { - // Else it is the default parameter set (Line breakpoint)... - Debug.Assert(ParameterSetName.Equals(LineParameterSetName, StringComparison.OrdinalIgnoreCase)); - - for (int i = 0; i < Line.Length; i++) - { - if (Line[i] < 1) - { - WriteError( - new ErrorRecord( - new ArgumentException(Debugger.LineLessThanOne), - "NewPSBreakpoint:LineLessThanOne", - ErrorCategory.InvalidArgument, - null)); - - continue; - } - - foreach (string path in scripts) - { - if (Column != 0) - { - WriteObject(new LineBreakpoint(path, Line[i], Column, Action)); - } - else - { - WriteObject(new LineBreakpoint(path, Line[i], Action)); - } - } - } - } - } - } -} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs index 23773fd4477..30e9ae7142d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs @@ -75,13 +75,13 @@ protected override void ProcessRecord() foreach (string path in scripts) { WriteObject( - Context.Debugger.NewCommandBreakpoint(path.ToString(), Command[i], Action)); + Context.Debugger.SetCommandBreakpoint(Command[i], Action, path)); } } else { WriteObject( - Context.Debugger.NewCommandBreakpoint(Command[i], Action)); + Context.Debugger.SetCommandBreakpoint(Command[i], Action)); } } } @@ -97,13 +97,13 @@ protected override void ProcessRecord() foreach (string path in scripts) { WriteObject( - Context.Debugger.NewVariableBreakpoint(path.ToString(), Variable[i], Mode, Action)); + Context.Debugger.SetVariableBreakpoint(Variable[i], Mode, Action, path)); } } else { WriteObject( - Context.Debugger.NewVariableBreakpoint(Variable[i], Mode, Action)); + Context.Debugger.SetVariableBreakpoint(Variable[i], Mode, Action)); } } } @@ -130,16 +130,8 @@ protected override void ProcessRecord() foreach (string path in scripts) { - if (Column != 0) - { - WriteObject( - Context.Debugger.NewStatementBreakpoint(path, Line[i], Column, Action)); - } - else - { - WriteObject( - Context.Debugger.NewLineBreakpoint(path, Line[i], Action)); - } + WriteObject( + Context.Debugger.SetLineBreakpoint(path, Line[i], Column, Action)); } } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs index 08dd6c311be..052f817c305 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs @@ -747,7 +747,7 @@ protected override void ProcessRecord() { if (_valueList == null) { - _valueList = new ArrayList(); + _valueList = new List(); } _valueList.Add(Value); @@ -759,7 +759,7 @@ protected override void ProcessRecord() } } - private ArrayList _valueList; + private List _valueList; /// /// Sets the variable if the name was specified as a formal parameter diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index 3e657818e99..1a727133309 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -204,7 +204,7 @@ internal static int Start( { ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("ServerMode"); ProfileOptimization.StartProfile("StartupProfileData-ServerMode"); - System.Management.Automation.Remoting.Server.OutOfProcessMediator.Run(s_cpp.InitialCommand); + System.Management.Automation.Remoting.Server.OutOfProcessMediator.Run(s_cpp.InitialCommand, s_cpp.WorkingDirectory); exitCode = 0; } else if (s_cpp.NamedPipeServerMode) @@ -2670,7 +2670,7 @@ private void EvaluateSuggestions(ConsoleHostUserInterface ui) // Output any training suggestions try { - ArrayList suggestions = HostUtilities.GetSuggestion(_parent.Runspace); + List suggestions = HostUtilities.GetSuggestion(_parent.Runspace); if (suggestions.Count > 0) { diff --git a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj index 307d115ae4d..163842a9c4e 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Microsoft.PowerShell.GlobalTool.Shim/GlobalToolShim.cs b/src/Microsoft.PowerShell.GlobalTool.Shim/GlobalToolShim.cs index 5f51e0644d5..79c13b4efc5 100644 --- a/src/Microsoft.PowerShell.GlobalTool.Shim/GlobalToolShim.cs +++ b/src/Microsoft.PowerShell.GlobalTool.Shim/GlobalToolShim.cs @@ -21,8 +21,9 @@ public class EntryPoint /// /// Entry point for the global tool. /// - /// Arguments passed to the global tool. - public static void Main(string[] args) + /// Arguments passed to the global tool.' + /// Exit code returned by pwsh. + public static int Main(string[] args) { var currentPath = new FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location).Directory.FullName; var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); @@ -31,11 +32,13 @@ public static void Main(string[] args) string argsString = args.Length > 0 ? string.Join(" ", args) : null; var pwshPath = Path.Combine(currentPath, platformFolder, PwshDllName); - string processArgs = string.IsNullOrEmpty(argsString) ? $"{pwshPath}" : $"{pwshPath} -c {argsString}"; + string processArgs = string.IsNullOrEmpty(argsString) ? $"\"{pwshPath}\"" : $"\"{pwshPath}\" {argsString}"; if (File.Exists(pwshPath)) { - System.Diagnostics.Process.Start("dotnet", processArgs).WaitForExit(); + var process = System.Diagnostics.Process.Start("dotnet", processArgs); + process.WaitForExit(); + return process.ExitCode; } else { diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index 439c57da49b..5a31f2cbb18 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -16,21 +16,21 @@ - + - - - + + + - - - - - - + + + + + + - + diff --git a/src/Microsoft.PowerShell.Security/security/CertificateCommands.cs b/src/Microsoft.PowerShell.Security/security/CertificateCommands.cs index 39595a5a353..91b2c583818 100644 --- a/src/Microsoft.PowerShell.Security/security/CertificateCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/CertificateCommands.cs @@ -80,7 +80,7 @@ public string[] LiteralPath // // list of files that were not found // - private ArrayList _filesNotFound = new ArrayList(); + private List _filesNotFound = new List(); /// /// Initializes a new instance of the GetPfxCertificateCommand diff --git a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj index 281623fc86d..d2158a4a9ae 100644 --- a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj +++ b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Modules/PSGalleryModules.csproj b/src/Modules/PSGalleryModules.csproj index bba8fba176b..e3f057662e2 100644 --- a/src/Modules/PSGalleryModules.csproj +++ b/src/Modules/PSGalleryModules.csproj @@ -6,8 +6,9 @@ - + + diff --git a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 index 267ddc928d5..34e7cc94ca6 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -18,7 +18,7 @@ CmdletsToExport = @( 'Set-MarkdownOption', 'Add-Member', 'Get-Member', 'Compare-Object', 'Group-Object', 'Measure-Object', 'New-Object', 'Select-Object', 'Sort-Object', 'Tee-Object', 'Register-ObjectEvent', 'Write-Output', 'Import-PowerShellDataFile', 'Write-Progress', 'Disable-PSBreakpoint', 'Enable-PSBreakpoint', - 'Get-PSBreakpoint', 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'New-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', + 'Get-PSBreakpoint', 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', 'Import-PSSession', 'Get-Random', 'Invoke-RestMethod', 'Debug-Runspace', 'Get-Runspace', 'Disable-RunspaceDebug', 'Enable-RunspaceDebug', 'Get-RunspaceDebug', 'Start-Sleep', 'Join-String', 'Out-String', 'Select-String', 'ConvertFrom-StringData', 'Format-Table', 'New-TemporaryFile', 'New-TimeSpan', @@ -35,8 +35,8 @@ PrivateData = @{ PSData = @{ ExperimentalFeatures = @( @{ - Name = 'Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints' - Description = "Enables the New-PSBreakpoint cmdlet and the -Breakpoint parameter on Debug-Runspace to set breakpoints in another Runspace upfront." + Name = 'Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace' + Description = 'Enables -BreakAll parameter on Debug-Runspace and Debug-Job cmdlets to allow users to decide if they want PowerShell to break immediately in the current location when they attach a debugger.' } ) } diff --git a/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 index 6ef4095acfc..a020aa17eae 100644 --- a/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -17,7 +17,7 @@ CmdletsToExport = @( 'Show-Markdown', 'Get-MarkdownOption', 'Set-MarkdownOption', 'Add-Member', 'Get-Member', 'Compare-Object', 'Group-Object', 'Measure-Object', 'New-Object', 'Select-Object', 'Sort-Object', 'Tee-Object', 'Register-ObjectEvent', 'Write-Output', 'Import-PowerShellDataFile', 'Write-Progress', 'Disable-PSBreakpoint', 'Enable-PSBreakpoint', 'Get-PSBreakpoint', - 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'New-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', 'Import-PSSession', 'Get-Random', + 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', 'Import-PSSession', 'Get-Random', 'Invoke-RestMethod', 'Debug-Runspace', 'Get-Runspace', 'Disable-RunspaceDebug', 'Enable-RunspaceDebug', 'Get-RunspaceDebug', 'ConvertFrom-SddlString', 'Start-Sleep', 'Join-String', 'Out-String', 'Select-String', 'ConvertFrom-StringData', 'Format-Table', 'New-TemporaryFile', 'New-TimeSpan', 'Get-TraceSource', 'Set-TraceSource', @@ -29,4 +29,14 @@ FunctionsToExport = @() AliasesToExport = @('fhx') NestedModules = @("Microsoft.PowerShell.Commands.Utility.dll") HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855960' +PrivateData = @{ + PSData = @{ + ExperimentalFeatures = @( + @{ + Name = 'Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace' + Description = 'Enables -BreakAll parameter on Debug-Runspace and Debug-Job cmdlets to allow users to decide if they want PowerShell to break immediately in the current location when they attach a debugger.' + } + ) + } +} } diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Certificate_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Certificate_format_ps1xml.cs index 07277a8cd46..412fe8deb5b 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Certificate_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Certificate_format_ps1xml.cs @@ -11,10 +11,9 @@ internal static IEnumerable GetFormatData() { var SignatureTypes_GroupingFormat = CustomControl.Create() .StartEntry() - .StartFrame(leftIndent: 4) + .StartFrame() .AddText(FileSystemProviderStrings.DirectoryDisplayGrouping) .AddScriptBlockExpressionBinding(@"split-path $_.Path") - .AddNewline() .EndFrame() .EndEntry() .EndControl(); diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs index 31a15c600f9..b65fde9eb64 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs @@ -11,12 +11,11 @@ internal static IEnumerable GetFormatData() { var FileSystemTypes_GroupingFormat = CustomControl.Create() .StartEntry() - .StartFrame(leftIndent: 4) + .StartFrame() .AddText(FileSystemProviderStrings.DirectoryDisplayGrouping) .AddScriptBlockExpressionBinding(@" $_.PSParentPath.Replace(""Microsoft.PowerShell.Core\FileSystem::"", """") ") - .AddNewline() .EndFrame() .EndEntry() .EndControl(); diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs index 82e9ba39d21..926aad50748 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs @@ -11,10 +11,9 @@ internal static IEnumerable GetFormatData() { var AvailableModules_GroupingFormat = CustomControl.Create() .StartEntry() - .StartFrame(leftIndent: 4) + .StartFrame() .AddText(FileSystemProviderStrings.DirectoryDisplayGrouping) .AddScriptBlockExpressionBinding(@"Split-Path -Parent $_.Path | ForEach-Object { if([Version]::TryParse((Split-Path $_ -Leaf), [ref]$null)) { Split-Path -Parent $_} else {$_} } | Split-Path -Parent") - .AddNewline() .EndFrame() .EndEntry() .EndControl(); diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Registry_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Registry_format_ps1xml.cs index 52a5cd8e4b1..caab800c85c 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Registry_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Registry_format_ps1xml.cs @@ -11,10 +11,9 @@ internal static IEnumerable GetFormatData() { var Registry_GroupingFormat = CustomControl.Create() .StartEntry() - .StartFrame(leftIndent: 4) - .AddText("Hive: ") + .StartFrame() + .AddText(" Hive: ") .AddScriptBlockExpressionBinding(@"$_.PSParentPath.Replace(""Microsoft.PowerShell.Core\Registry::"", """")") - .AddNewline() .EndFrame() .EndEntry() .EndControl(); diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs index 1b1dae636ab..be70dc9714b 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs @@ -481,6 +481,7 @@ private void ProcessGroupStart(FormatMessagesContextManager.OutputContext c) ComplexWriter writer = new ComplexWriter(); writer.Initialize(_lo, _lo.ColumnNumber); writer.WriteObject(goc.Data.groupingEntry.formatValueList); + _lo.WriteLine(string.Empty); } goc.GroupStart(); diff --git a/src/System.Management.Automation/FormatAndOutput/out-console/OutConsole.cs b/src/System.Management.Automation/FormatAndOutput/out-console/OutConsole.cs index d8dca91267b..ef5da004555 100644 --- a/src/System.Management.Automation/FormatAndOutput/out-console/OutConsole.cs +++ b/src/System.Management.Automation/FormatAndOutput/out-console/OutConsole.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System; -using System.Collections; +using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Internal; @@ -88,7 +88,7 @@ protected override void BeginProcessing() if (Context.CurrentCommandProcessor.CommandRuntime.OutVarList != null) { - _outVarResults = new ArrayList(); + _outVarResults = new List(); } } @@ -162,7 +162,7 @@ protected override void InternalDispose() } } - private ArrayList _outVarResults = null; + private List _outVarResults = null; private IDisposable _transcribeOnlyCookie = null; } diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index 6700e342a5d..c94c51b1fa4 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -16,19 +16,19 @@ - - - - - - - - - - + + + + + + + + + + - + diff --git a/src/System.Management.Automation/engine/ArgumentTypeConverterAttribute.cs b/src/System.Management.Automation/engine/ArgumentTypeConverterAttribute.cs index 19c383e6cd2..746d2f82df3 100644 --- a/src/System.Management.Automation/engine/ArgumentTypeConverterAttribute.cs +++ b/src/System.Management.Automation/engine/ArgumentTypeConverterAttribute.cs @@ -147,15 +147,14 @@ internal object Transform(EngineIntrinsics engineIntrinsics, object inputData, b // Note - this is duplicated in ExecutionContext.cs as parameter binding for script cmdlets can avoid this code path. if ((!bindingScriptCmdlet) && (!bindingParameters)) { - // ActionPreference of Suspend is not supported as a preference variable. We can only block "Suspend" - // during variable assignment (here) - "Ignore" is blocked during variable retrieval. + // ActionPreference.Suspend is reserved for future use and is not supported as a preference variable. if (_convertTypes[i] == typeof(ActionPreference)) { ActionPreference resultPreference = (ActionPreference)result; if (resultPreference == ActionPreference.Suspend) { - throw new PSInvalidCastException("InvalidActionPreference", null, ErrorPackage.UnsupportedPreferenceVariable, resultPreference); + throw new PSInvalidCastException("InvalidActionPreference", null, ErrorPackage.ActionPreferenceReservedForFutureUseError, resultPreference); } } } diff --git a/src/System.Management.Automation/engine/AutomationEngine.cs b/src/System.Management.Automation/engine/AutomationEngine.cs index b1e8e6ef889..0f04d99057f 100644 --- a/src/System.Management.Automation/engine/AutomationEngine.cs +++ b/src/System.Management.Automation/engine/AutomationEngine.cs @@ -36,30 +36,22 @@ internal class AutomationEngine internal AutomationEngine(PSHost hostInterface, InitialSessionState iss) { #if !UNIX - // Update the env variable PathEXT to contain .CPL - var pathext = Environment.GetEnvironmentVariable("PathEXT"); - pathext = pathext ?? string.Empty; - bool cplExist = false; - if (pathext != string.Empty) + // Update the env variable PATHEXT to contain .CPL + var pathext = Environment.GetEnvironmentVariable("PATHEXT"); + + if (string.IsNullOrEmpty(pathext)) { - string[] entries = pathext.Split(Utils.Separators.Semicolon); - foreach (string entry in entries) - { - string ext = entry.Trim(); - if (ext.Equals(".CPL", StringComparison.OrdinalIgnoreCase)) - { - cplExist = true; - break; - } - } + Environment.SetEnvironmentVariable("PATHEXT", ".CPL"); } - - if (!cplExist) + else if (!(pathext.EndsWith(";.CPL", StringComparison.OrdinalIgnoreCase) || + pathext.StartsWith(".CPL;", StringComparison.OrdinalIgnoreCase) || + pathext.Contains(";.CPL;", StringComparison.OrdinalIgnoreCase) || + pathext.Equals(".CPL", StringComparison.OrdinalIgnoreCase))) { - pathext = (pathext == string.Empty) ? ".CPL" : - pathext.EndsWith(";", StringComparison.OrdinalIgnoreCase) - ? (pathext + ".CPL") : (pathext + ";.CPL"); - Environment.SetEnvironmentVariable("PathEXT", pathext); + // Fast skip if we already added the extention as ";.CPL". + // Fast skip if user already added the extention. + pathext += pathext[pathext.Length - 1] == ';' ? ".CPL" : ";.CPL"; + Environment.SetEnvironmentVariable("PATHEXT", pathext); } #endif @@ -69,9 +61,7 @@ internal AutomationEngine(PSHost hostInterface, InitialSessionState iss) CommandDiscovery = new CommandDiscovery(Context); // Load the iss, resetting everything to it's defaults... - iss.Bind(Context, /*updateOnly*/ false); - - InitialSessionState.SetSessionStateDrive(Context, true); + iss.Bind(Context, updateOnly: false, module: null, noClobber: false, local: false, setLocation: true); } /// diff --git a/src/System.Management.Automation/engine/CommandBase.cs b/src/System.Management.Automation/engine/CommandBase.cs index 8848d86f3d9..b2351059cc9 100644 --- a/src/System.Management.Automation/engine/CommandBase.cs +++ b/src/System.Management.Automation/engine/CommandBase.cs @@ -291,7 +291,7 @@ public enum ActionPreference /// Ignore the event completely (not even logging it to the target stream) Ignore = 4, - /// Suspend the command for further diagnosis. Supported only for workflows. + /// Reserved for future use. Suspend = 5, /// Enter the debugger. diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 2119e513259..2034244aa9d 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -6991,6 +6991,13 @@ public object VisitPipeline(PipelineAst pipelineAst) return expr != null && (bool)expr.Accept(this); } + public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + return (bool)ternaryExpressionAst.Condition.Accept(this) && + (bool)ternaryExpressionAst.IfTrue.Accept(this) && + (bool)ternaryExpressionAst.IfFalse.Accept(this); + } + public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { return (bool)binaryExpressionAst.Left.Accept(this) && (bool)binaryExpressionAst.Right.Accept(this); diff --git a/src/System.Management.Automation/engine/ExecutionContext.cs b/src/System.Management.Automation/engine/ExecutionContext.cs index 45cb3bd7fa5..ac6fea07a0b 100644 --- a/src/System.Management.Automation/engine/ExecutionContext.cs +++ b/src/System.Management.Automation/engine/ExecutionContext.cs @@ -573,18 +573,9 @@ internal T GetEnumPreference(VariablePath preferenceVariablePath, T defaultPr object val = EngineSessionState.GetVariableValue(preferenceVariablePath, out _, out _); if (val is T) { - // We don't want to support "Ignore" as action preferences, as it leads to bad - // scripting habits. They are only supported as cmdlet overrides. - if (val is ActionPreference) + if (val is ActionPreference actionPreferenceValue) { - ActionPreference preference = (ActionPreference)val; - if ((preference == ActionPreference.Ignore) || (preference == ActionPreference.Suspend)) - { - // Reset the variable value - EngineSessionState.SetVariableValue(preferenceVariablePath.UserPath, defaultPref); - string message = StringUtil.Format(ErrorPackage.UnsupportedPreferenceError, preference); - throw new NotSupportedException(message); - } + CheckActionPreference(preferenceVariablePath, actionPreferenceValue, defaultPref); } T convertedResult = (T)val; @@ -611,6 +602,11 @@ internal T GetEnumPreference(VariablePath preferenceVariablePath, T defaultPr result = (T)PSObject.Base(val); defaultUsed = false; } + + if (result is ActionPreference actionPreferenceValue) + { + CheckActionPreference(preferenceVariablePath, actionPreferenceValue, defaultPref); + } } catch (InvalidCastException) { @@ -625,6 +621,18 @@ internal T GetEnumPreference(VariablePath preferenceVariablePath, T defaultPr return result; } + private void CheckActionPreference(VariablePath preferenceVariablePath, ActionPreference preference, object defaultValue) + { + if (preference == ActionPreference.Suspend) + { + // ActionPreference.Suspend is reserved for future use. When it is used, reset + // the variable to its default. + string message = StringUtil.Format(ErrorPackage.ReservedActionPreferenceReplacedError, preference, preferenceVariablePath.UserPath, defaultValue); + EngineSessionState.SetVariable(preferenceVariablePath, defaultValue, true, CommandOrigin.Internal); + throw new NotSupportedException(message); + } + } + /// /// Same as GetEnumPreference, but for boolean values. /// diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 676c7decea2..71b0d0f86b9 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -111,7 +111,10 @@ static ExperimentalFeature() description: "Recommend potential commands based on fuzzy search on a CommandNotFoundException"), new ExperimentalFeature( name: "PSForEachObjectParallel", - description: "New parameter set for ForEach-Object to run script blocks in parallel") + description: "New parameter set for ForEach-Object to run script blocks in parallel"), + new ExperimentalFeature( + name: "PSTernaryOperator", + description: "Support the ternary operator in PowerShell language") }; EngineExperimentalFeatures = new ReadOnlyCollection(engineFeatures); diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 3a82abaef96..631cbb315be 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -2097,12 +2097,7 @@ public virtual HashSet StartupScripts private object _syncObject = new Object(); - internal void Bind(ExecutionContext context, bool updateOnly) - { - Bind(context, updateOnly, null, /*noClobber*/false, /*local*/ false); - } - - internal void Bind(ExecutionContext context, bool updateOnly, PSModuleInfo module, bool noClobber, bool local) + internal void Bind(ExecutionContext context, bool updateOnly, PSModuleInfo module, bool noClobber, bool local, bool setLocation) { Host = context.EngineHostInterface; lock (_syncObject) @@ -2205,7 +2200,7 @@ internal void Bind(ExecutionContext context, bool updateOnly, PSModuleInfo modul } } - SetSessionStateDrive(context, setLocation: false); + SetSessionStateDrive(context, setLocation: setLocation); } private void Bind_SetVariables(SessionStateInternal ss) @@ -4083,7 +4078,9 @@ internal static string GetClearHostFunctionText() { // Porting note: non-Windows platforms use `clear` return @" -& (Get-Command -CommandType Application clear | Select-Object -First 1).Definition +[Console]::Write(( + & (Get-Command -CommandType Application clear | Select-Object -First 1).Definition +)) # .Link # https://go.microsoft.com/fwlink/?LinkID=225747 # .ExternalHelp System.Management.Automation.dll-help.xml @@ -4550,7 +4547,6 @@ internal static SessionStateAliasEntry[] BuiltInAliases new SessionStateAliasEntry("mi", "Move-Item", string.Empty, ReadOnly), new SessionStateAliasEntry("mp", "Move-ItemProperty", string.Empty, ReadOnly), new SessionStateAliasEntry("nal", "New-Alias", string.Empty, ReadOnly), - new SessionStateAliasEntry("nbp", "New-PSBreakpoint", string.Empty, ReadOnly), new SessionStateAliasEntry("ndr", "New-PSDrive", string.Empty, ReadOnly), new SessionStateAliasEntry("ni", "New-Item", string.Empty, ReadOnly), new SessionStateAliasEntry("nv", "New-Variable", string.Empty, ReadOnly), diff --git a/src/System.Management.Automation/engine/InternalCommands.cs b/src/System.Management.Automation/engine/InternalCommands.cs index 010f1e1f9cf..78338e08cc8 100644 --- a/src/System.Management.Automation/engine/InternalCommands.cs +++ b/src/System.Management.Automation/engine/InternalCommands.cs @@ -70,6 +70,8 @@ public sealed class ForEachObjectCommand : PSCmdlet, IDisposable #endregion + #region Common Parameters + /// /// This parameter specifies the current pipeline object. /// @@ -85,6 +87,8 @@ public PSObject InputObject private PSObject _inputObject = AutomationNull.Value; + #endregion + #region ScriptBlockSet private List _scripts = new List(); @@ -355,6 +359,7 @@ public void Dispose() _taskTimer?.Dispose(); _taskDataStreamWriter?.Dispose(); _taskPool?.Dispose(); + _taskCollection?.Dispose(); } #endregion @@ -368,6 +373,8 @@ public void Dispose() private Dictionary _usingValuesMap; private Timer _taskTimer; private PSTaskJob _taskJob; + private PSDataCollection _taskCollection; + private Exception _taskCollectionException; private void InitParallelParameterSet() { @@ -411,6 +418,7 @@ private void InitParallelParameterSet() if (AsJob) { + // Set up for returning a job object. if (MyInvocation.BoundParameters.ContainsKey(nameof(TimeoutSeconds))) { ThrowTerminatingError( @@ -424,30 +432,85 @@ private void InitParallelParameterSet() _taskJob = new PSTaskJob( Parallel.ToString(), ThrottleLimit); + + return; } - else + + // Set up for synchronous processing and data streaming. + _taskCollection = new PSDataCollection(); + _taskDataStreamWriter = new PSTaskDataStreamWriter(this); + _taskPool = new PSTaskPool(ThrottleLimit); + _taskPool.PoolComplete += (sender, args) => { - _taskDataStreamWriter = new PSTaskDataStreamWriter(this); - _taskPool = new PSTaskPool(ThrottleLimit); - _taskPool.PoolComplete += (sender, args) => - { - _taskDataStreamWriter.Close(); - }; - if (TimeoutSeconds != 0) - { - _taskTimer = new Timer( - (_) => _taskPool.StopAll(), - null, - TimeoutSeconds * 1000, - Timeout.Infinite); - } + _taskDataStreamWriter.Close(); + }; + + // Create timeout timer if requested. + if (TimeoutSeconds != 0) + { + _taskTimer = new Timer( + callback: (_) => { _taskCollection.Complete(); _taskPool.StopAll(); }, + state: null, + dueTime: TimeoutSeconds * 1000, + period: Timeout.Infinite); } + + // Task collection handler. + System.Threading.ThreadPool.QueueUserWorkItem( + (_) => + { + // As piped input are converted to PSTasks and added to the _taskCollection, + // transfer the task to the _taskPool on this dedicated thread. + // The _taskPool will block this thread when it is full, and allow more tasks to + // be added only when a currently running task completes and makes space in the pool. + // Continue adding any tasks appearing in _taskCollection until the collection is closed. + while (true) + { + // This handle will unblock the thread when a new task is available or the _taskCollection + // is closed. + _taskCollection.WaitHandle.WaitOne(); + + // Task collection open state is volatile. + // Record current task collection open state here, to be checked after processing. + bool isOpen = _taskCollection.IsOpen; + + try + { + // Read all tasks in the collection. + foreach (var task in _taskCollection.ReadAll()) + { + // This _taskPool method will block if the pool is full and will unblock + // only after a task completes making more space. + _taskPool.Add(task); + } + } + catch (Exception ex) + { + // Close the _taskCollection on an unexpected exception so the pool closes and + // lets any running tasks complete. + _taskCollection.Complete(); + _taskCollectionException = ex; + break; + } + + // Loop is exited only when task collection is closed and all task + // collection tasks are processed. + if (!isOpen) + { + break; + } + } + + // We are done adding tasks and can close the task pool. + _taskPool.Close(); + }); } private void ProcessParallelParameterSet() { // Validate piped InputObject - if (_inputObject.BaseObject is ScriptBlock) + if (_inputObject != null && + _inputObject.BaseObject is ScriptBlock) { WriteError( new ErrorRecord( @@ -461,27 +524,39 @@ private void ProcessParallelParameterSet() if (AsJob) { + // Add child task job. var taskChildJob = new PSTaskChildJob( Parallel, _usingValuesMap, InputObject); _taskJob.AddJob(taskChildJob); + + return; } - else - { - // Write any streaming data - _taskDataStreamWriter.WriteImmediate(); - var task = new System.Management.Automation.PSTasks.PSTask( - Parallel, - _usingValuesMap, - InputObject, - _taskDataStreamWriter); + // Write any streaming data + _taskDataStreamWriter.WriteImmediate(); - // Add task to task pool. - // Block if the pool is full and wait until task can be added. - _taskPool.Add(task, _taskDataStreamWriter); + // Add to task collection for processing. + if (_taskCollection.IsOpen) + { + try + { + // Create a PSTask based on this piped input and add it to the task collection. + // A dedicated thread will add it to the PSTask pool in a performant manner. + _taskCollection.Add( + new System.Management.Automation.PSTasks.PSTask( + Parallel, + _usingValuesMap, + InputObject, + _taskDataStreamWriter)); + } + catch (InvalidOperationException) + { + // This exception is thrown if the task collection is closed, which should not happen. + Dbg.Assert(false, "Should not add to a closed PSTask collection"); + } } } @@ -489,25 +564,37 @@ private void EndParallelParameterSet() { if (AsJob) { + // Start and return parent job object. _taskJob.Start(); JobRepository.Add(_taskJob); WriteObject(_taskJob); + + return; } - else - { - _taskDataStreamWriter.WriteImmediate(); + + // Close task collection and wait for processing to complete while streaming data. + _taskDataStreamWriter.WriteImmediate(); + _taskCollection.Complete(); + _taskDataStreamWriter.WaitAndWrite(); - _taskPool.Close(); - _taskDataStreamWriter.WaitAndWrite(); + // Check for an unexpected error from the _taskCollection handler thread and report here. + var ex = _taskCollectionException; + if (ex != null) + { + var msg = string.Format(CultureInfo.InvariantCulture, InternalCommandStrings.ParallelPipedInputProcessingError, ex); + WriteError( + new ErrorRecord( + exception: new InvalidOperationException(msg), + errorId: "ParallelPipedInputProcessingError", + errorCategory: ErrorCategory.InvalidOperation, + targetObject: this)); } } private void StopParallelProcessing() { - if (!AsJob) - { - _taskPool.StopAll(); - } + _taskCollection?.Complete(); + _taskPool?.StopAll(); } #endregion diff --git a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs index e2312555e6e..cd755e55e4a 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs @@ -2396,7 +2396,7 @@ internal PSModuleInfo LoadModuleManifest( { try { - iss.Bind(Context, /*updateOnly*/ true); + iss.Bind(Context, updateOnly: true, module: null, noClobber: false, local: false, setLocation: false); } catch (Exception e) { @@ -6765,7 +6765,7 @@ internal PSModuleInfo LoadBinaryModule(PSModuleInfo parentModule, bool trySnapIn iss.DisableFormatUpdates = true; // Load the cmdlets and providers, bound to the new module... - iss.Bind(Context, /*updateOnly*/ true, module, options.NoClobber, options.Local); + iss.Bind(Context, updateOnly: true, module, options.NoClobber, options.Local, setLocation: false); // Scan all of the types in the assembly to register JobSourceAdapters. IEnumerable allTypes = new Type[] { }; diff --git a/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs b/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs index e1b579e6505..8893882c80d 100644 --- a/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs +++ b/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs @@ -258,6 +258,8 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a public override AstVisitAction VisitSwitchStatement(SwitchStatementAst switchStatementAst) { return AstVisitAction.SkipChildren; } + public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) { return AstVisitAction.SkipChildren; } + // Visit one the other variations: // - Dotting scripts // - Setting aliases diff --git a/src/System.Management.Automation/engine/MshCommandRuntime.cs b/src/System.Management.Automation/engine/MshCommandRuntime.cs index 07208dc3435..f9e4b6bae6e 100644 --- a/src/System.Management.Automation/engine/MshCommandRuntime.cs +++ b/src/System.Management.Automation/engine/MshCommandRuntime.cs @@ -3009,6 +3009,11 @@ internal ActionPreference DebugPreference set { + if (value == ActionPreference.Suspend) + { + throw PSTraceSource.NewNotSupportedException(ErrorPackage.ActionPreferenceReservedForFutureUseError, value); + } + _debugPreference = value; _isDebugPreferenceSet = true; } @@ -3098,7 +3103,7 @@ internal ActionPreference WarningPreference { if (value == ActionPreference.Suspend) { - throw PSTraceSource.NewNotSupportedException(ErrorPackage.SuspendActionPreferenceErrorActionOnly); + throw PSTraceSource.NewNotSupportedException(ErrorPackage.ActionPreferenceReservedForFutureUseError, value); } _warningPreference = value; @@ -3268,7 +3273,7 @@ internal ActionPreference ErrorAction { if (value == ActionPreference.Suspend) { - throw PSTraceSource.NewNotSupportedException(ErrorPackage.SuspendActionPreferenceSupportedOnlyOnWorkflow); + throw PSTraceSource.NewNotSupportedException(ErrorPackage.ActionPreferenceReservedForFutureUseError, value); } _errorAction = value; @@ -3301,6 +3306,11 @@ internal ActionPreference ProgressPreference set { + if (value == ActionPreference.Suspend) + { + throw PSTraceSource.NewNotSupportedException(ErrorPackage.ActionPreferenceReservedForFutureUseError, value); + } + _progressPreference = value; _isProgressPreferenceSet = true; } @@ -3335,7 +3345,7 @@ internal ActionPreference InformationPreference { if (value == ActionPreference.Suspend) { - throw PSTraceSource.NewNotSupportedException(ErrorPackage.SuspendActionPreferenceErrorActionOnly); + throw PSTraceSource.NewNotSupportedException(ErrorPackage.ActionPreferenceReservedForFutureUseError, value); } _informationPreference = value; diff --git a/src/System.Management.Automation/engine/PSVersionInfo.cs b/src/System.Management.Automation/engine/PSVersionInfo.cs index ef58dafb56a..7ac1c4b94ba 100644 --- a/src/System.Management.Automation/engine/PSVersionInfo.cs +++ b/src/System.Management.Automation/engine/PSVersionInfo.cs @@ -61,6 +61,7 @@ public class PSVersionInfo private static readonly SemanticVersion s_psV6Version = new SemanticVersion(6, 0, 0, preReleaseLabel: null, buildLabel: null); private static readonly SemanticVersion s_psV61Version = new SemanticVersion(6, 1, 0, preReleaseLabel: null, buildLabel: null); private static readonly SemanticVersion s_psV62Version = new SemanticVersion(6, 2, 0, preReleaseLabel: null, buildLabel: null); + private static readonly SemanticVersion s_psV7Version = new SemanticVersion(7, 0, 0, preReleaseLabel: null, buildLabel: null); private static readonly SemanticVersion s_psSemVersion; private static readonly Version s_psVersion; @@ -329,6 +330,11 @@ internal static SemanticVersion PSV6Version get { return s_psV6Version; } } + internal static SemanticVersion PSV7Version + { + get { return s_psV7Version; } + } + internal static SemanticVersion PSCurrentVersion { get { return s_psSemVersion; } diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index 829870c6925..c8557d91d7b 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -807,40 +807,43 @@ internal static bool IsValidPSEditionValue(string editionValue) {nameof(ConsoleSessionConfiguration), @"Software\Policies\Microsoft\PowerShellCore\ConsoleSessionConfiguration"} }; + private static readonly Dictionary WindowsPowershellGroupPolicyKeys = new Dictionary + { + { nameof(ScriptExecution), @"Software\Policies\Microsoft\Windows\PowerShell" }, + { nameof(ScriptBlockLogging), @"Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" }, + { nameof(ModuleLogging), @"Software\Policies\Microsoft\Windows\PowerShell\ModuleLogging" }, + { nameof(Transcription), @"Software\Policies\Microsoft\Windows\PowerShell\Transcription" }, + { nameof(UpdatableHelp), @"Software\Policies\Microsoft\Windows\PowerShell\UpdatableHelp" }, + }; + + private const string PolicySettingFallbackKey = "UseWindowsPowerShellPolicySetting"; + private static readonly ConcurrentDictionary> s_cachedPoliciesFromRegistry = new ConcurrentDictionary>(); private static readonly Func> s_subCacheCreationDelegate = - key => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + key => new ConcurrentDictionary(StringComparer.Ordinal); /// - /// The implementation of fetching a specific kind of policy setting from the given configuration scope. + /// Read policy settings from a registry key into a policy object. /// - private static T GetPolicySettingFromGPOImpl(ConfigScope scope) where T : PolicyBase, new() + /// Policy object that will be filled with values from registry. + /// Type of policy object used. + /// Registry key that has policy settings. + /// True if any property was successfully set on the policy object. + private static bool TrySetPolicySettingsFromRegistryKey(object instance, Type instanceType, RegistryKey gpoKey) { - Type tType = typeof(T); - // SystemWide scope means 'LocalMachine' root key when query from registry - RegistryKey rootKey = (scope == ConfigScope.AllUsers) ? Registry.LocalMachine : Registry.CurrentUser; + var properties = instanceType.GetProperties(BindingFlags.Instance | BindingFlags.Public); + bool isAnyPropertySet = false; - GroupPolicyKeys.TryGetValue(tType.Name, out string gpoKeyPath); - Diagnostics.Assert(gpoKeyPath != null, StringUtil.Format("The GPO registry key path should be pre-defined for {0}", tType.Name)); + string[] valueNames = gpoKey.GetValueNames(); + string[] subKeyNames = gpoKey.GetSubKeyNames(); + var valueNameSet = valueNames.Length > 0 ? new HashSet(valueNames, StringComparer.OrdinalIgnoreCase) : null; + var subKeyNameSet = subKeyNames.Length > 0 ? new HashSet(subKeyNames, StringComparer.OrdinalIgnoreCase) : null; - using (RegistryKey gpoKey = rootKey.OpenSubKey(gpoKeyPath)) + // If there are any values or subkeys in the registry key - read them into the policy instance object + if ((valueNameSet != null) || (subKeyNameSet != null)) { - // If the corresponding GPO key doesn't exist, return null - if (gpoKey == null) { return null; } - - // The corresponding GPO key exists, then create an instance of T - // and populate its properties with the settings - object tInstance = Activator.CreateInstance(tType, nonPublic: true); - var properties = tType.GetProperties(BindingFlags.Instance | BindingFlags.Public); - bool isAnyPropertySet = false; - - string[] valueNames = gpoKey.GetValueNames(); - string[] subKeyNames = gpoKey.GetSubKeyNames(); - var valueNameSet = valueNames.Length > 0 ? new HashSet(valueNames, StringComparer.OrdinalIgnoreCase) : null; - var subKeyNameSet = subKeyNames.Length > 0 ? new HashSet(subKeyNames, StringComparer.OrdinalIgnoreCase) : null; - foreach (var property in properties) { string settingName = property.Name; @@ -855,7 +858,10 @@ internal static bool IsValidPSEditionValue(string editionValue) { using (RegistryKey subKey = gpoKey.OpenSubKey(settingName)) { - if (subKey != null) { rawRegistryValue = subKey.GetValueNames(); } + if (subKey != null) + { + rawRegistryValue = subKey.GetValueNames(); + } } } @@ -871,8 +877,14 @@ internal static bool IsValidPSEditionValue(string editionValue) case var _ when propertyType == typeof(bool?): if (rawRegistryValue is int rawIntValue) { - if (rawIntValue == 1) { propertyValue = true; } - else if (rawIntValue == 0) { propertyValue = false; } + if (rawIntValue == 1) + { + propertyValue = true; + } + else if (rawIntValue == 0) + { + propertyValue = false; + } } break; @@ -895,18 +907,61 @@ internal static bool IsValidPSEditionValue(string editionValue) break; default: - Diagnostics.Assert(false, "Should be unreachable code. Update this switch block when properties of new types are added to PowerShell policy types."); - break; + throw System.Management.Automation.Interpreter.Assert.Unreachable; } // Set the property if the value is not null if (propertyValue != null) { - property.SetValue(tInstance, propertyValue); + property.SetValue(instance, propertyValue); isAnyPropertySet = true; } } } + } + + return isAnyPropertySet; + } + + /// + /// The implementation of fetching a specific kind of policy setting from the given configuration scope. + /// + private static T GetPolicySettingFromGPOImpl(ConfigScope scope) where T : PolicyBase, new() + { + Type tType = typeof(T); + // SystemWide scope means 'LocalMachine' root key when query from registry + RegistryKey rootKey = (scope == ConfigScope.AllUsers) ? Registry.LocalMachine : Registry.CurrentUser; + + GroupPolicyKeys.TryGetValue(tType.Name, out string gpoKeyPath); + Diagnostics.Assert(gpoKeyPath != null, StringUtil.Format("The GPO registry key path should be pre-defined for {0}", tType.Name)); + + using (RegistryKey gpoKey = rootKey.OpenSubKey(gpoKeyPath)) + { + // If the corresponding GPO key doesn't exist, return null + if (gpoKey == null) { return null; } + + // The corresponding GPO key exists, then create an instance of T + // and populate its properties with the settings + object tInstance = Activator.CreateInstance(tType, nonPublic: true); + bool isAnyPropertySet = false; + + // if PolicySettingFallbackKey is Not set - use PowerShell Core policy reg key + if ((int)gpoKey.GetValue(PolicySettingFallbackKey, 0) == 0) + { + isAnyPropertySet = TrySetPolicySettingsFromRegistryKey(tInstance, tType, gpoKey); + } + else + { + // when PolicySettingFallbackKey flag is set (REG_DWORD "1") use Windows PS policy reg key + WindowsPowershellGroupPolicyKeys.TryGetValue(tType.Name, out string winPowershellGpoKeyPath); + Diagnostics.Assert(winPowershellGpoKeyPath != null, StringUtil.Format("The Windows PS GPO registry key path should be pre-defined for {0}", tType.Name)); + using (RegistryKey winPowershellGpoKey = rootKey.OpenSubKey(winPowershellGpoKeyPath)) + { + // If the corresponding Windows PS GPO key doesn't exist, return null + if (winPowershellGpoKey == null) { return null; } + isAnyPropertySet = TrySetPolicySettingsFromRegistryKey(tInstance, tType, winPowershellGpoKey); + } + } // If no property is set, then we consider this policy as undefined return isAnyPropertySet ? (T)tInstance : null; diff --git a/src/System.Management.Automation/engine/debugger/Breakpoint.cs b/src/System.Management.Automation/engine/debugger/Breakpoint.cs index 80fc66430a6..30d83e57693 100644 --- a/src/System.Management.Automation/engine/debugger/Breakpoint.cs +++ b/src/System.Management.Automation/engine/debugger/Breakpoint.cs @@ -128,9 +128,7 @@ internal BreakpointAction Trigger() return BreakpointAction.Continue; } - internal virtual void RemoveSelf(ScriptDebugger debugger) - { - } + internal virtual bool RemoveSelf(ScriptDebugger debugger) => false; #endregion methods @@ -208,10 +206,8 @@ public override string ToString() : StringUtil.Format(DebuggerStrings.CommandBreakpointString, Command); } - internal override void RemoveSelf(ScriptDebugger debugger) - { + internal override bool RemoveSelf(ScriptDebugger debugger) => debugger.RemoveCommandBreakpoint(this); - } private bool CommandInfoMatches(CommandInfo commandInfo) { @@ -350,10 +346,8 @@ internal bool Trigger(string currentScriptFile, bool read) return false; } - internal override void RemoveSelf(ScriptDebugger debugger) - { + internal override bool RemoveSelf(ScriptDebugger debugger) => debugger.RemoveVariableBreakpoint(this); - } } /// @@ -589,7 +583,7 @@ private void SetBreakpoint(FunctionContext functionContext, int sequencePointInd this.BreakpointBitArray.Set(SequencePointIndex, true); } - internal override void RemoveSelf(ScriptDebugger debugger) + internal override bool RemoveSelf(ScriptDebugger debugger) { if (this.SequencePoints != null) { @@ -612,7 +606,7 @@ internal override void RemoveSelf(ScriptDebugger debugger) } } - debugger.RemoveLineBreakpoint(this); + return debugger.RemoveLineBreakpoint(this); } } } diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index 60be22a00d0..8468edb0c0f 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -619,30 +619,73 @@ public virtual IEnumerable GetCallStack() } /// - /// Adds the provided set of breakpoints to the debugger. + /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. /// - /// Breakpoints. - public virtual void SetBreakpoints(IEnumerable breakpoints) - { + /// Id of the breakpoint you want. + public virtual Breakpoint GetBreakpoint(int id) => throw new PSNotImplementedException(); - } /// - /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. + /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. /// - /// Id of the breakpoint you want. - public virtual Breakpoint GetBreakpoint(int id) - { + public virtual List GetBreakpoints() => throw new PSNotImplementedException(); - } /// - /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. + /// Sets a command breakpoint in the debugger. /// - public virtual List GetBreakpoints() - { + /// The name of the command that will trigger the breakpoint. This value is required and may not be null. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the command is invoked. + /// The command breakpoint that was set. + public virtual CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action = null, string path = null) => + throw new PSNotImplementedException(); + + /// + /// Sets a line breakpoint in the debugger. + /// + /// The path to the script file where the breakpoint may be hit. This value is required and may not be null. + /// The line in the script file where the breakpoint may be hit. This value is required and must be greater than or equal to 1. + /// The column in the script file where the breakpoint may be hit. If 0, the breakpoint will trigger on any statement on the line. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The line breakpoint that was set. + public virtual LineBreakpoint SetLineBreakpoint(string path, int line, int column = 0, ScriptBlock action = null) => + throw new PSNotImplementedException(); + + /// + /// Sets a variable breakpoint in the debugger. + /// + /// The name of the variable that will trigger the breakpoint. This value is required and may not be null. + /// The variable access mode that will trigger the breakpoint. By default variable breakpoints will trigger only when the variable is updated. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the variable is accessed using the specified access mode. + /// The variable breakpoint that was set. + public virtual VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode = VariableAccessMode.Write, ScriptBlock action = null, string path = null) => + throw new PSNotImplementedException(); + + /// + /// Removes a breakpoint from the debugger. + /// + /// The breakpoint to remove from the debugger. This value is required and may not be null. + /// True if the breakpoint was removed from the debugger; false otherwise. + public virtual bool RemoveBreakpoint(Breakpoint breakpoint) => + throw new PSNotImplementedException(); + + /// + /// Enables a breakpoint in the debugger. + /// + /// The breakpoint to enable in the debugger. This value is required and may not be null. + /// The updated breakpoint if it was found; null if the breakpoint was not found in the debugger. + public virtual Breakpoint EnableBreakpoint(Breakpoint breakpoint) => + throw new PSNotImplementedException(); + + /// + /// Disables a breakpoint in the debugger. + /// + /// The breakpoint to enable in the debugger. This value is required and may not be null. + /// The updated breakpoint if it was found; null if the breakpoint was not found in the debugger. + public virtual Breakpoint DisableBreakpoint(Breakpoint breakpoint) => throw new PSNotImplementedException(); - } /// /// Resets the command processor source information so that it is @@ -704,13 +747,15 @@ internal virtual bool InternalProcessListCommand(int lineNum, IList ou /// Sets up debugger to debug provided job or its child jobs. /// /// - /// Job object that is either a debuggable job or a container - /// of debuggable child jobs. + /// Job object that is either a debuggable job or a container of + /// debuggable child jobs. /// - internal virtual void DebugJob(Job job) - { + /// + /// If true, the debugger automatically invokes a break all when it + /// attaches to the job. + /// + internal virtual void DebugJob(Job job, bool breakAll) => throw new PSNotImplementedException(); - } /// /// Removes job from debugger job list and pops the its @@ -763,21 +808,15 @@ internal virtual void ReleaseSavedDebugStop() /// /// Sets up debugger to debug provided Runspace in a nested debug session. /// - /// Runspace to debug. - internal virtual void DebugRunspace(Runspace runspace) - { - throw new PSNotImplementedException(); - } - - /// - /// Sets up debugger to debug provided Runspace in a nested debug session. - /// - /// Runspace to debug. - /// - internal virtual void DebugRunspace(Runspace runspace, bool disableBreakAll) - { + /// + /// The runspace to debug. + /// + /// + /// If true, the debugger automatically invokes a break all when it + /// attaches to the runspace. + /// + internal virtual void DebugRunspace(Runspace runspace, bool breakAll) => throw new PSNotImplementedException(); - } /// /// Removes the provided Runspace from the nested "active" debugger state. @@ -1122,7 +1161,7 @@ internal void RegisterScriptFile(string path, string scriptContents) #endregion Call stack management - #region adding breakpoints + #region setting breakpoints internal void AddBreakpointCommon(Breakpoint breakpoint) { @@ -1135,30 +1174,14 @@ internal void AddBreakpointCommon(Breakpoint breakpoint) OnBreakpointUpdated(new BreakpointUpdatedEventArgs(breakpoint, BreakpointUpdateType.Set, _idToBreakpoint.Count)); } - private Breakpoint AddCommandBreakpoint(CommandBreakpoint breakpoint) + private CommandBreakpoint AddCommandBreakpoint(CommandBreakpoint breakpoint) { AddBreakpointCommon(breakpoint); _commandBreakpoints[breakpoint.Id] = breakpoint; return breakpoint; } - internal Breakpoint NewCommandBreakpoint(string path, string command, ScriptBlock action) - { - WildcardPattern pattern = WildcardPattern.Get(command, WildcardOptions.Compiled | WildcardOptions.IgnoreCase); - - CheckForBreakpointSupport(); - return AddCommandBreakpoint(new CommandBreakpoint(path, pattern, command, action)); - } - - internal Breakpoint NewCommandBreakpoint(string command, ScriptBlock action) - { - WildcardPattern pattern = WildcardPattern.Get(command, WildcardOptions.Compiled | WildcardOptions.IgnoreCase); - - CheckForBreakpointSupport(); - return AddCommandBreakpoint(new CommandBreakpoint(null, pattern, command, action)); - } - - private Breakpoint AddLineBreakpoint(LineBreakpoint breakpoint) + private LineBreakpoint AddLineBreakpoint(LineBreakpoint breakpoint) { AddBreakpointCommon(breakpoint); _pendingBreakpoints[breakpoint.Id] = breakpoint; @@ -1188,22 +1211,6 @@ private void AddNewBreakpoint(Breakpoint breakpoint) } } - internal Breakpoint NewLineBreakpoint(string path, int line, ScriptBlock action) - { - Diagnostics.Assert(path != null, "caller to verify path is not null"); - - CheckForBreakpointSupport(); - return AddLineBreakpoint(new LineBreakpoint(path, line, action)); - } - - internal Breakpoint NewStatementBreakpoint(string path, int line, int column, ScriptBlock action) - { - Diagnostics.Assert(path != null, "caller to verify path is not null"); - - CheckForBreakpointSupport(); - return AddLineBreakpoint(new LineBreakpoint(path, line, column, action)); - } - internal VariableBreakpoint AddVariableBreakpoint(VariableBreakpoint breakpoint) { AddBreakpointCommon(breakpoint); @@ -1218,16 +1225,30 @@ internal VariableBreakpoint AddVariableBreakpoint(VariableBreakpoint breakpoint) return breakpoint; } - internal Breakpoint NewVariableBreakpoint(string path, string variableName, VariableAccessMode accessMode, ScriptBlock action) + private void UpdateBreakpoints(FunctionContext functionContext) { - CheckForBreakpointSupport(); - return AddVariableBreakpoint(new VariableBreakpoint(path, variableName, accessMode, action)); - } + if (functionContext._breakPoints == null) + { + // This should be rare - setting a breakpoint inside a script, but debugger hadn't started. + SetupBreakpoints(functionContext); + } + else + { + // Check pending breakpoints to see if any apply to this script. + if (string.IsNullOrEmpty(functionContext._file)) + { + return; + } - internal Breakpoint NewVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action) - { - CheckForBreakpointSupport(); - return AddVariableBreakpoint(new VariableBreakpoint(null, variableName, accessMode, action)); + foreach ((int breakpointId, LineBreakpoint item) in _pendingBreakpoints) + { + if (item.IsScriptBreakpoint && item.Script.Equals(functionContext._file, StringComparison.OrdinalIgnoreCase)) + { + SetPendingBreakpoints(functionContext); + break; + } + } + } } /// @@ -1239,44 +1260,27 @@ private void OnBreakpointUpdated(BreakpointUpdatedEventArgs e) RaiseBreakpointUpdatedEvent(e); } - #endregion adding breakpoints + #endregion setting breakpoints #region removing breakpoints - // This is the implementation of the Remove-PSBreakpoint cmdlet. - internal void RemoveBreakpoint(Breakpoint breakpoint) - { - _idToBreakpoint.Remove(breakpoint.Id, out _); - - breakpoint.RemoveSelf(this); - - if (CanDisableDebugger) - { - SetInternalDebugMode(InternalDebugMode.Disabled); - } - - OnBreakpointUpdated(new BreakpointUpdatedEventArgs(breakpoint, BreakpointUpdateType.Removed, _idToBreakpoint.Count)); - } - - internal void RemoveVariableBreakpoint(VariableBreakpoint breakpoint) - { + internal bool RemoveVariableBreakpoint(VariableBreakpoint breakpoint) => _variableBreakpoints[breakpoint.Variable].Remove(breakpoint.Id, out _); - } - internal void RemoveCommandBreakpoint(CommandBreakpoint breakpoint) - { + internal bool RemoveCommandBreakpoint(CommandBreakpoint breakpoint) => _commandBreakpoints.Remove(breakpoint.Id, out _); - } - internal void RemoveLineBreakpoint(LineBreakpoint breakpoint) + internal bool RemoveLineBreakpoint(LineBreakpoint breakpoint) { - _pendingBreakpoints.Remove(breakpoint.Id, out _); + bool removed = _pendingBreakpoints.Remove(breakpoint.Id, out _); Tuple> value; if (_boundBreakpoints.TryGetValue(breakpoint.Script, out value)) { - value.Item2.Remove(breakpoint.Id, out _); + removed = value.Item2.Remove(breakpoint.Id, out _); } + + return removed; } #endregion removing breakpoints @@ -1386,24 +1390,6 @@ internal void TriggerVariableBreakpoints(List breakpoints) OnDebuggerStop(invocationInfo, breakpoints.ToList()); } - /// - /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. - /// - /// Id of the breakpoint you want. - public override Breakpoint GetBreakpoint(int id) - { - _idToBreakpoint.TryGetValue(id, out Breakpoint breakpoint); - return breakpoint; - } - - /// - /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. - /// - public override List GetBreakpoints() - { - return (from bp in _idToBreakpoint.Values orderby bp.Id select bp).ToList(); - } - // Return the line breakpoints bound in a specific script block (used when a sequence point // is hit, to find which breakpoints are set on that sequence point.) internal List GetBoundBreakpoints(IScriptExtent[] sequencePoints) @@ -1458,30 +1444,6 @@ private List TriggerBreakpoints(List breakpoints) return breaks; } - #endregion triggering breakpoints - - #region enabling/disabling breakpoints - - /// - /// Implementation of Enable-PSBreakpoint cmdlet. - /// - internal void EnableBreakpoint(Breakpoint bp) - { - bp.SetEnabled(true); - OnBreakpointUpdated(new BreakpointUpdatedEventArgs(bp, BreakpointUpdateType.Enabled, _idToBreakpoint.Count)); - } - - /// - /// Implementation of Disable-PSBreakpoint cmdlet. - /// - internal void DisableBreakpoint(Breakpoint bp) - { - bp.SetEnabled(false); - OnBreakpointUpdated(new BreakpointUpdatedEventArgs(bp, BreakpointUpdateType.Disabled, _idToBreakpoint.Count)); - } - - #endregion enabling/disabling breakpoints - internal void OnSequencePointHit(FunctionContext functionContext) { if (_context.ShouldTraceStatement && !_callStack.Last().IsFrameHidden && !functionContext._debuggerStepThrough) @@ -1533,34 +1495,7 @@ internal void OnSequencePointHit(FunctionContext functionContext) } } - private void UpdateBreakpoints(FunctionContext functionContext) - { - if (functionContext._breakPoints == null) - { - // This should be rare - setting a breakpoint inside a script, but debugger hadn't started. - SetupBreakpoints(functionContext); - } - else - { - // Check pending breakpoints to see if any apply to this script. - if (string.IsNullOrEmpty(functionContext._file)) { return; } - - bool havePendingBreakpoint = false; - foreach ((int breakpointId, LineBreakpoint item) in _pendingBreakpoints) - { - if (item.IsScriptBreakpoint && item.Script.Equals(functionContext._file, StringComparison.OrdinalIgnoreCase)) - { - havePendingBreakpoint = true; - break; - } - } - - if (havePendingBreakpoint) - { - SetPendingBreakpoints(functionContext); - } - } - } + #endregion triggering breakpoints #endregion internal methods @@ -2448,39 +2383,6 @@ public override IEnumerable GetCallStack() } } - /// - /// SetBreakpoints. - /// - /// - public override void SetBreakpoints(IEnumerable breakpoints) - { - if (breakpoints == null) - { - throw new PSArgumentNullException("breakpoints"); - } - - foreach (var breakpoint in breakpoints) - { - if (_idToBreakpoint.ContainsKey(breakpoint.Id)) { continue; } - - switch (breakpoint) - { - case LineBreakpoint lineBp: - AddLineBreakpoint(lineBp); - continue; - case CommandBreakpoint cmdBp: - AddCommandBreakpoint(cmdBp); - continue; - case VariableBreakpoint variableBp: - AddVariableBreakpoint(variableBp); - continue; - default: - // Unreachable default block - break; - } - } - } - /// /// True when debugger is active with breakpoints. /// @@ -2676,16 +2578,147 @@ internal override UnhandledBreakpointProcessingMode UnhandledBreakpointMode } } + #region Breakpoints + + /// + /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. + /// + /// Id of the breakpoint you want. + public override Breakpoint GetBreakpoint(int id) + { + _idToBreakpoint.TryGetValue(id, out Breakpoint breakpoint); + return breakpoint; + } + + /// + /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. + /// + public override List GetBreakpoints() + { + return (from bp in _idToBreakpoint.Values orderby bp.Id select bp).ToList(); + } + + /// + /// Sets a command breakpoint in the debugger. + /// + /// The name of the command that will trigger the breakpoint. This value is required and may not be null. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the command is invoked. + /// + public override CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action = null, string path = null) + { + Diagnostics.Assert(!string.IsNullOrEmpty(command), "Caller to verify command is not null or empty."); + + WildcardPattern pattern = WildcardPattern.Get(command, WildcardOptions.Compiled | WildcardOptions.IgnoreCase); + + CheckForBreakpointSupport(); + return AddCommandBreakpoint(new CommandBreakpoint(path, pattern, command, action)); + } + + /// + /// Sets a line breakpoint in the debugger. + /// + /// The path to the script file where the breakpoint may be hit. This value is required and may not be null. + /// The line in the script file where the breakpoint may be hit. This value is required and must be greater than or equal to 1. + /// The column in the script file where the breakpoint may be hit. If 0, the breakpoint will trigger on any statement on the line. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// + public override LineBreakpoint SetLineBreakpoint(string path, int line, int column = 0, ScriptBlock action = null) + { + Diagnostics.Assert(path != null, "Caller to verify path is not null."); + Diagnostics.Assert(line > 0, "Caller to verify line is greater than 0."); + + CheckForBreakpointSupport(); + return AddLineBreakpoint(new LineBreakpoint(path, line, column, action)); + } + + /// + /// Sets a variable breakpoint in the debugger. + /// + /// The name of the variable that will trigger the breakpoint. This value is required and may not be null. + /// The variable access mode that will trigger the breakpoint. By default variable breakpoints will trigger only when the variable is updated. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the variable is accessed using the specified access mode. + /// + public override VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode = VariableAccessMode.Write, ScriptBlock action = null, string path = null) + { + Diagnostics.Assert(!string.IsNullOrEmpty(variableName), "Caller to verify variableName is not null or empty."); + + CheckForBreakpointSupport(); + return AddVariableBreakpoint(new VariableBreakpoint(path, variableName, accessMode, action)); + } + + // This is the implementation of the Remove-PSBreakpoint cmdlet. + public override bool RemoveBreakpoint(Breakpoint breakpoint) + { + Diagnostics.Assert(breakpoint != null, "Caller to verify the breakpoint is not null."); + + if (_idToBreakpoint.Remove(breakpoint.Id, out _)) + { + breakpoint.RemoveSelf(this); + + if (CanDisableDebugger) + { + SetInternalDebugMode(InternalDebugMode.Disabled); + } + + OnBreakpointUpdated(new BreakpointUpdatedEventArgs(breakpoint, BreakpointUpdateType.Removed, _idToBreakpoint.Count)); + + return true; + } + + return false; + } + + + // This is the implementation of the Enable-PSBreakpoint cmdlet. + public override Breakpoint EnableBreakpoint(Breakpoint breakpoint) + { + Diagnostics.Assert(breakpoint != null, "Caller to verify the breakpoint is not null."); + + if (_idToBreakpoint.TryGetValue(breakpoint.Id, out _)) + { + breakpoint.SetEnabled(true); + OnBreakpointUpdated(new BreakpointUpdatedEventArgs(breakpoint, BreakpointUpdateType.Enabled, _idToBreakpoint.Count)); + + return breakpoint; + } + + return null; + } + + // This is the implementation of the Disable-PSBreakpoint cmdlet. + public override Breakpoint DisableBreakpoint(Breakpoint breakpoint) + { + Diagnostics.Assert(breakpoint != null, "Caller to verify the breakpoint is not null."); + + if (_idToBreakpoint.TryGetValue(breakpoint.Id, out _)) + { + breakpoint.SetEnabled(false); + OnBreakpointUpdated(new BreakpointUpdatedEventArgs(breakpoint, BreakpointUpdateType.Disabled, _idToBreakpoint.Count)); + + return breakpoint; + } + + return null; + } + + #endregion Breakpoints + #region Job Debugging /// /// Sets up debugger to debug provided job or its child jobs. /// /// - /// Job object that is either a debuggable job or a container - /// of debuggable child jobs. + /// Job object that is either a debuggable job or a container of + /// debuggable child jobs. /// - internal override void DebugJob(Job job) + /// + /// If true, the debugger automatically invokes a break all when it + /// attaches to the job. + /// + internal override void DebugJob(Job job, bool breakAll) { if (job == null) { throw new PSArgumentNullException("job"); } @@ -2699,13 +2732,13 @@ internal override void DebugJob(Job job) // If a debuggable job was passed in then add it to the // job running list. - bool jobsAdded = TryAddDebugJob(job); + bool jobsAdded = TryAddDebugJob(job, breakAll); if (!jobsAdded) { // Otherwise treat as parent Job and iterate over child jobs. foreach (Job childJob in job.ChildJobs) { - if (TryAddDebugJob(childJob) && !jobsAdded) + if (TryAddDebugJob(childJob, breakAll) && !jobsAdded) { jobsAdded = true; } @@ -2718,7 +2751,7 @@ internal override void DebugJob(Job job) } } - private bool TryAddDebugJob(Job job) + private bool TryAddDebugJob(Job job, bool breakAll) { IJobDebugger debuggableJob = job as IJobDebugger; if ((debuggableJob != null) && (debuggableJob.Debugger != null) && @@ -2731,7 +2764,7 @@ private bool TryAddDebugJob(Job job) SetDebugJobAsync(debuggableJob, false); AddToJobRunningList( new PSJobStartEventArgs(job, debuggableJob.Debugger, false), - DebuggerResumeAction.StepInto); + breakAll ? DebuggerResumeAction.StepInto : DebuggerResumeAction.Continue); // Raise debug stop event if job is already in stopped state. if (jobDebugAlreadyStopped) @@ -2794,17 +2827,17 @@ internal static void SetDebugJobAsync(IJobDebugger debuggableJob, bool isAsync) #region Runspace Debugging - internal override void DebugRunspace(Runspace runspace) - { - DebugRunspace(runspace, disableBreakAll: false); - } - /// /// Sets up debugger to debug provided Runspace in a nested debug session. /// - /// Runspace to debug. - /// When specified, it will not turn on BreakAll. - internal override void DebugRunspace(Runspace runspace, bool disableBreakAll) + /// + /// Runspace to debug. + /// + /// + /// When true, this command will invoke a BreakAll when the debugger is + /// first attached. + /// + internal override void DebugRunspace(Runspace runspace, bool breakAll) { if (runspace == null) { @@ -2839,7 +2872,7 @@ internal override void DebugRunspace(Runspace runspace, bool disableBreakAll) AddToRunningRunspaceList(new PSStandaloneMonitorRunspaceInfo(runspace)); - if (!runspace.Debugger.InBreakpoint && !disableBreakAll) + if (!runspace.Debugger.InBreakpoint && breakAll) { EnableDebuggerStepping(EnableNestedType.NestedRunspace); } @@ -2980,7 +3013,6 @@ private void AddToJobRunningList(PSJobStartEventArgs jobArgs, DebuggerResumeActi _runningJobs.Add(jobArgs.Job.InstanceId, jobArgs); jobArgs.Debugger.DebuggerStop += HandleMonitorRunningJobsDebuggerStop; - jobArgs.Debugger.BreakpointUpdated += HandleBreakpointUpdated; newJob = true; } @@ -3057,7 +3089,6 @@ private void RemoveFromRunningJobList(Job job) if (_runningJobs.TryGetValue(job.InstanceId, out jobArgs)) { jobArgs.Debugger.DebuggerStop -= HandleMonitorRunningJobsDebuggerStop; - jobArgs.Debugger.BreakpointUpdated -= HandleBreakpointUpdated; _runningJobs.Remove(job.InstanceId); } } @@ -3275,28 +3306,6 @@ private bool IsJobDebuggingMode() (((DebugMode & DebugModes.RemoteScript) == DebugModes.RemoteScript) && !IsLocalSession)); } - private void HandleBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) - { - switch (e.UpdateType) - { - case BreakpointUpdateType.Set: - AddNewBreakpoint(e.Breakpoint); - break; - - case BreakpointUpdateType.Removed: - RemoveBreakpoint(e.Breakpoint); - break; - - case BreakpointUpdateType.Enabled: - EnableBreakpoint(e.Breakpoint); - break; - - case BreakpointUpdateType.Disabled: - DisableBreakpoint(e.Breakpoint); - break; - } - } - private bool IsRunningWFJobsDebugger(Debugger debugger) { lock (_syncObject) @@ -3495,7 +3504,6 @@ private void RemoveFromRunningRunspaceList(Runspace runspace) if (nestedDebugger != null) { nestedDebugger.DebuggerStop -= HandleMonitorRunningRSDebuggerStop; - nestedDebugger.BreakpointUpdated -= HandleBreakpointUpdated; nestedDebugger.Dispose(); // If current active debugger, then pop. @@ -3665,7 +3673,6 @@ private bool SetUpDebuggerOnRunspace(Runspace runspace) runspaceInfo.NestedDebugger = nestedDebugger; nestedDebugger.DebuggerStop += HandleMonitorRunningRSDebuggerStop; - nestedDebugger.BreakpointUpdated += HandleBreakpointUpdated; if (((_lastActiveDebuggerAction == DebuggerResumeAction.StepInto) || (_currentDebuggerAction == DebuggerResumeAction.StepInto)) && !nestedDebugger.IsActive) @@ -3745,7 +3752,7 @@ private void ProcessRunspaceDebugInternally(Runspace runspace) { WaitForReadyDebug(); - DebugRunspace(runspace); + DebugRunspace(runspace, breakAll:true); // Block this event thread until debugging has ended. WaitForDebugComplete(); @@ -4077,28 +4084,30 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC return _wrappedDebugger.ProcessCommand(command, output); } - /// - /// Adds the provided set of breakpoints to the debugger. - /// - /// Breakpoints. - public override void SetBreakpoints(IEnumerable breakpoints) - { - _wrappedDebugger.SetBreakpoints(breakpoints); - } - - /// - /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. - /// - /// Id of the breakpoint you want. public override Breakpoint GetBreakpoint(int id) => _wrappedDebugger.GetBreakpoint(id); - /// - /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. - /// public override List GetBreakpoints() => _wrappedDebugger.GetBreakpoints(); + public override CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action = null, string path = null) => + _wrappedDebugger.SetCommandBreakpoint(command, action, path); + + public override LineBreakpoint SetLineBreakpoint(string path, int line, int column = 0, ScriptBlock action = null) => + _wrappedDebugger.SetLineBreakpoint(path, line, column, action); + + public override VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode = VariableAccessMode.Write, ScriptBlock action = null, string path = null) => + _wrappedDebugger.SetVariableBreakpoint(variableName, accessMode, action, path); + + public override bool RemoveBreakpoint(Breakpoint breakpoint) => + _wrappedDebugger.RemoveBreakpoint(breakpoint); + + public override Breakpoint EnableBreakpoint(Breakpoint breakpoint) => + _wrappedDebugger.EnableBreakpoint(breakpoint); + + public override Breakpoint DisableBreakpoint(Breakpoint breakpoint) => + _wrappedDebugger.DisableBreakpoint(breakpoint); + /// /// SetDebuggerAction. /// @@ -5397,12 +5406,6 @@ namespace System.Management.Automation.Internal [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Needed Internal use only")] public static class DebuggerUtils { - internal const string SetDebugModeFunctionName = "__Set-PSDebugMode"; - internal const string SetDebuggerActionFunctionName = "__Set-PSDebuggerAction"; - internal const string GetDebuggerStopArgsFunctionName = "__Get-PSDebuggerStopArgs"; - internal const string SetDebuggerStepMode = "__Set-PSDebuggerStepMode"; - internal const string SetPSUnhandledBreakpointMode = "__Set-PSUnhandledBreakpointMode"; - private static SortedSet s_noHistoryCommandNames = new SortedSet(StringComparer.OrdinalIgnoreCase) { "prompt", diff --git a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs index 22f25edb590..63eaa9b8b98 100644 --- a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs +++ b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs @@ -72,11 +72,11 @@ public static class HostUtilities $formatString -f [string]::Join(', ', (Get-Command $lastError.TargetObject -UseFuzzyMatch | Select-Object -First 10 -Unique -ExpandProperty Name)) "; - private static ArrayList s_suggestions = InitializeSuggestions(); + private static List s_suggestions = InitializeSuggestions(); - private static ArrayList InitializeSuggestions() + private static List InitializeSuggestions() { - ArrayList suggestions = new ArrayList( + var suggestions = new List( new Hashtable[] { NewSuggestion( @@ -307,10 +307,10 @@ internal static string GetMaxLines(string source, int maxLines) return returnValue.ToString(); } - internal static ArrayList GetSuggestion(Runspace runspace) + internal static List GetSuggestion(Runspace runspace) { LocalRunspace localRunspace = runspace as LocalRunspace; - if (localRunspace == null) { return new ArrayList(); } + if (localRunspace == null) { return new List(); } // Get the last value of $? bool questionMarkVariableValue = localRunspace.ExecutionContext.QuestionMarkVariableValue; @@ -320,7 +320,7 @@ internal static ArrayList GetSuggestion(Runspace runspace) HistoryInfo[] entries = history.GetEntries(-1, 1, true); if (entries.Length == 0) - return new ArrayList(); + return new List(); HistoryInfo lastHistory = entries[0]; @@ -363,7 +363,7 @@ internal static ArrayList GetSuggestion(Runspace runspace) Runspace.DefaultRunspace = runspace; } - ArrayList suggestions = null; + List suggestions = null; try { @@ -383,9 +383,9 @@ internal static ArrayList GetSuggestion(Runspace runspace) } [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] - internal static ArrayList GetSuggestion(HistoryInfo lastHistory, object lastError, ArrayList errorList) + internal static List GetSuggestion(HistoryInfo lastHistory, object lastError, ArrayList errorList) { - ArrayList returnSuggestions = new ArrayList(); + var returnSuggestions = new List(); PSModuleInfo invocationModule = new PSModuleInfo(true); invocationModule.SessionState.PSVariable.Set("lastHistory", lastHistory); diff --git a/src/System.Management.Automation/engine/hostifaces/PSTask.cs b/src/System.Management.Automation/engine/hostifaces/PSTask.cs index 61f66d75e5d..e0adf784577 100644 --- a/src/System.Management.Automation/engine/hostifaces/PSTask.cs +++ b/src/System.Management.Automation/engine/hostifaces/PSTask.cs @@ -40,7 +40,7 @@ public PSTask( ScriptBlock scriptBlock, Dictionary usingValuesMap, object dollarUnderbar, - PSTaskDataStreamWriter dataStreamWriter) + PSTaskDataStreamWriter dataStreamWriter) : base( scriptBlock, usingValuesMap, @@ -127,7 +127,7 @@ private void HandleInformationData() new PSStreamObject(PSStreamObjectType.Information, item)); } } - + #endregion #region Event handlers @@ -361,8 +361,8 @@ public PSInvocationState State #region Constructor - private PSTaskBase() - { + private PSTaskBase() + { _id = Interlocked.Increment(ref s_taskId); } @@ -422,7 +422,7 @@ public void Start() // Create and open Runspace for this task to run in var iss = InitialSessionState.CreateDefault2(); - iss.LanguageMode = (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) + iss.LanguageMode = (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) ? PSLanguageMode.ConstrainedLanguage : PSLanguageMode.FullLanguage; _runspace = RunspaceFactory.CreateRunspace(iss); _runspace.Name = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", RunspaceName, s_taskId); @@ -475,7 +475,7 @@ internal sealed class PSTaskDataStreamWriter : IDisposable private readonly PSCmdlet _cmdlet; private readonly PSDataCollection _dataStream; private readonly int _cmdletThreadId; - + #endregion #region Properties @@ -602,12 +602,16 @@ internal sealed class PSTaskPool : IDisposable #region Members private readonly ManualResetEvent _addAvailable; - private readonly ManualResetEvent _stopAll; - private readonly Dictionary _taskPool; private readonly int _sizeLimit; + private readonly ManualResetEvent _stopAll; private readonly object _syncObject; + private readonly Dictionary _taskPool; + private readonly WaitHandle[] _waitHandles; private bool _isOpen; + private const int AddAvailable = 0; + private const int Stop = 1; + #endregion #region Constructor @@ -625,6 +629,11 @@ public PSTaskPool(int size) _syncObject = new object(); _addAvailable = new ManualResetEvent(true); _stopAll = new ManualResetEvent(false); + _waitHandles = new WaitHandle[] + { + _addAvailable, // index 0 + _stopAll, // index 1 + }; _taskPool = new Dictionary(size); } @@ -672,46 +681,21 @@ public void Dispose() /// This method is not multi-thread safe and assumes only one thread waits and adds tasks. /// /// Task to be added to pool. - /// Optional cmdlet data stream writer. /// True when task is successfully added. - public bool Add( - PSTaskBase task, - PSTaskDataStreamWriter dataStreamWriter = null) + public bool Add(PSTaskBase task) { if (!_isOpen) { return false; } - WaitHandle[] waitHandles; - if (dataStreamWriter != null) - { - waitHandles = new WaitHandle[] - { - _addAvailable, // index 0 - _stopAll, // index 1 - dataStreamWriter.DataAddedWaitHandle // index 2 - }; - } - else - { - waitHandles = new WaitHandle[] - { - _addAvailable, // index 0 - _stopAll, // index 1 - }; - } + // Block until either space is available, or a stop is commanded + var index = WaitHandle.WaitAny(_waitHandles); - // Block until either room is available, data is ready for writing, or a stop command - while (true) + switch (index) { - var index = WaitHandle.WaitAny(waitHandles); - - // Add new task - if (index == 0) - { + case AddAvailable: task.StateChanged += HandleTaskStateChangedDelegate; - lock (_syncObject) { if (!_isOpen) @@ -729,19 +713,12 @@ public bool Add( } return true; - } - // Stop all - if (index == 1) - { + case Stop: + return false; + + default: return false; - } - - // Data ready for writing - if (index == 2) - { - dataStreamWriter.WriteImmediate(); - } } } @@ -763,7 +740,7 @@ public void StopAll() // Accept no more input Close(); _stopAll.Set(); - + // Stop all running tasks lock (_syncObject) { @@ -788,7 +765,7 @@ public void Close() #region Private Methods private void HandleTaskStateChangedDelegate(object sender, PSInvocationStateChangedEventArgs args) => HandleTaskStateChanged(sender, args); - + private void HandleTaskStateChanged(object sender, PSInvocationStateChangedEventArgs args) { var task = sender as PSTaskBase; @@ -829,10 +806,10 @@ private void CheckForComplete() try { PoolComplete.SafeInvoke( - this, + this, new EventArgs()); } - catch + catch { Dbg.Assert(false, "Exceptions should not be thrown on event thread"); } @@ -896,8 +873,8 @@ public override string Location /// public override bool HasMoreData { - get - { + get + { foreach (var childJob in ChildJobs) { if (childJob.HasMoreData) @@ -925,7 +902,7 @@ public override void StopJob() { _stopSignaled = true; SetJobState(JobState.Stopping); - + _taskPool.StopAll(); SetJobState(JobState.Stopped); } @@ -977,7 +954,7 @@ public void Start() // This thread will end once all jobs reach a finished state by either running // to completion, terminating with error, or stopped. System.Threading.ThreadPool.QueueUserWorkItem( - (state) => + (_) => { foreach (var childJob in ChildJobs) { @@ -1015,7 +992,7 @@ private void HandleTaskPoolComplete(object sender, EventArgs args) SetJobState(finalState); } - catch (ObjectDisposedException) + catch (ObjectDisposedException) { } } @@ -1084,15 +1061,6 @@ public override DebuggerCommandResults ProcessCommand( return _wrappedDebugger.ProcessCommand(command, output); } - /// - /// Adds the provided set of breakpoints to the debugger. - /// - /// List of breakpoints. - public override void SetBreakpoints(IEnumerable breakpoints) - { - _wrappedDebugger.SetBreakpoints(breakpoints); - } - /// /// Sets the debugger resume action. /// @@ -1102,6 +1070,27 @@ public override void SetDebuggerAction(DebuggerResumeAction resumeAction) _wrappedDebugger.SetDebuggerAction(resumeAction); } + public override Breakpoint GetBreakpoint(int id) => + _wrappedDebugger.GetBreakpoint(id); + + public override CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action = null, string path = null) => + _wrappedDebugger.SetCommandBreakpoint(command, action, path); + + public override VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode = VariableAccessMode.Write, ScriptBlock action = null, string path = null) => + _wrappedDebugger.SetVariableBreakpoint(variableName, accessMode, action, path); + + public override LineBreakpoint SetLineBreakpoint(string path, int line, int column = 0, ScriptBlock action = null) => + _wrappedDebugger.SetLineBreakpoint(path, line, column, action); + + public override Breakpoint EnableBreakpoint(Breakpoint breakpoint) => + _wrappedDebugger.EnableBreakpoint(breakpoint); + + public override Breakpoint DisableBreakpoint(Breakpoint breakpoint) => + _wrappedDebugger.DisableBreakpoint(breakpoint); + + public override bool RemoveBreakpoint(Breakpoint breakpoint) => + _wrappedDebugger.RemoveBreakpoint(breakpoint); + /// /// Stops a running command. /// diff --git a/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs b/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs index 3f82bea8ee2..2ec10fa7705 100644 --- a/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs +++ b/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs @@ -41,15 +41,26 @@ static PowerShellProcessInstance() } /// + /// Initializes a new instance of the class. Initializes the underlying dotnet process class. /// - /// - /// - /// - /// - public PowerShellProcessInstance(Version powerShellVersion, PSCredential credential, ScriptBlock initializationScript, bool useWow64) + /// Specifies the version of powershell. + /// Specifies a user account credentials. + /// Specifies a script that will be executed when the powershell process is initialized. + /// Specifies if the powershell process will be 32-bit. + /// Specifies the initial working directory for the new powershell process. + public PowerShellProcessInstance(Version powerShellVersion, PSCredential credential, ScriptBlock initializationScript, bool useWow64, string workingDirectory) { string processArguments = " -s -NoLogo -NoProfile"; + if (!string.IsNullOrWhiteSpace(workingDirectory)) + { + processArguments = string.Format( + CultureInfo.InvariantCulture, + "{0} -wd {1}", + processArguments, + workingDirectory); + } + if (initializationScript != null) { string scripBlockAsString = initializationScript.ToString(); @@ -57,8 +68,11 @@ public PowerShellProcessInstance(Version powerShellVersion, PSCredential credent { string encodedCommand = Convert.ToBase64String(Encoding.Unicode.GetBytes(scripBlockAsString)); - processArguments = string.Format(CultureInfo.InvariantCulture, - "{0} -EncodedCommand {1}", processArguments, encodedCommand); + processArguments = string.Format( + CultureInfo.InvariantCulture, + "{0} -EncodedCommand {1}", + processArguments, + encodedCommand); } } @@ -91,8 +105,20 @@ public PowerShellProcessInstance(Version powerShellVersion, PSCredential credent } /// + /// Initializes a new instance of the class. Initializes the underlying dotnet process class. + /// + /// + /// + /// + /// + public PowerShellProcessInstance(Version powerShellVersion, PSCredential credential, ScriptBlock initializationScript, bool useWow64) : this(powerShellVersion, credential, initializationScript, useWow64, workingDirectory: null) + { + } + + /// + /// Initializes a new instance of the class. Default initializes the underlying dotnet process class. /// - public PowerShellProcessInstance() : this(null, null, null, false) + public PowerShellProcessInstance() : this(powerShellVersion: null, credential: null, initializationScript: null, useWow64: false, workingDirectory: null) { } diff --git a/src/System.Management.Automation/engine/parser/AstVisitor.cs b/src/System.Management.Automation/engine/parser/AstVisitor.cs index 446eaa95c56..2f8dfeff4cb 100644 --- a/src/System.Management.Automation/engine/parser/AstVisitor.cs +++ b/src/System.Management.Automation/engine/parser/AstVisitor.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reflection; using System.Reflection.Emit; namespace System.Management.Automation.Language @@ -151,6 +150,8 @@ public interface ICustomAstVisitor /// public interface ICustomAstVisitor2 : ICustomAstVisitor { + private object DefaultVisit(Ast ast) => null; + /// object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst); @@ -171,6 +172,9 @@ public interface ICustomAstVisitor2 : ICustomAstVisitor /// object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst); + + /// + object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) => DefaultVisit(ternaryExpressionAst); } #if DEBUG @@ -312,6 +316,8 @@ internal AstVisitAction CheckParent(Ast ast) public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst ast) { return CheckParent(ast); } public override AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatementAst ast) { return CheckParent(ast); } + + public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst ast) => CheckParent(ast); } /// @@ -544,6 +550,8 @@ protected AstVisitAction CheckScriptBlock(Ast ast) public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst ast) { return Check(ast); } public override AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatementAst ast) { return Check(ast); } + + public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst ast) { return Check(ast); } } /// @@ -680,5 +688,7 @@ public abstract class DefaultCustomAstVisitor2 : DefaultCustomAstVisitor, ICusto public virtual object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { return null; } /// public virtual object VisitFunctionMember(FunctionMemberAst functionMemberAst) { return null; } + /// + public virtual object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) { return null; } } } diff --git a/src/System.Management.Automation/engine/parser/CharTraits.cs b/src/System.Management.Automation/engine/parser/CharTraits.cs index 058ffac9811..1130a3909bb 100644 --- a/src/System.Management.Automation/engine/parser/CharTraits.cs +++ b/src/System.Management.Automation/engine/parser/CharTraits.cs @@ -374,13 +374,26 @@ internal static bool ForceStartNewToken(this char c) return c.IsWhitespace(); } - // Return true if the character ends the current number token. This allows the tokenizer - // to scan '7z' as a single token, but '7+' as 2 tokens. - internal static bool ForceStartNewTokenAfterNumber(this char c) + /// + /// Check if the current character forces to end scanning a number token. + /// This allows the tokenizer to scan '7z' as a single token, but '7+' as 2 tokens. + /// + /// The character to check. + /// + /// In some cases, we want '?' and ':' to end a number token too, so they can be + /// treated as the ternary operator tokens. + /// + /// Return true if the character ends the current number token. + internal static bool ForceStartNewTokenAfterNumber(this char c, bool forceEndNumberOnTernaryOperatorChars) { if (c < 128) { - return (s_traits[c] & CharTraits.ForceStartNewTokenAfterNumber) != 0; + if ((s_traits[c] & CharTraits.ForceStartNewTokenAfterNumber) != 0) + { + return true; + } + + return forceEndNumberOnTernaryOperatorChars && (c == '?' || c == ':'); } return c.IsDash(); diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index bc4d4f3d81e..c06e23c5c5d 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -4974,6 +4974,16 @@ public Expression GenerateCallContains(Expression lhs, Expression rhs, bool igno rhs.Cast(typeof(object))); } + public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + var expr = Expression.Condition( + Compile(ternaryExpressionAst.Condition).Convert(typeof(bool)), + Compile(ternaryExpressionAst.IfTrue).Convert(typeof(object)), + Compile(ternaryExpressionAst.IfFalse).Convert(typeof(object))); + + return expr; + } + public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { object constantValue; diff --git a/src/System.Management.Automation/engine/parser/ConstantValues.cs b/src/System.Management.Automation/engine/parser/ConstantValues.cs index 74c83f93980..16d4a8d271e 100644 --- a/src/System.Management.Automation/engine/parser/ConstantValues.cs +++ b/src/System.Management.Automation/engine/parser/ConstantValues.cs @@ -15,7 +15,7 @@ namespace System.Management.Automation.Language * There is a number of similarities between these two classes, and changes (fixes) in this code * may need to be reflected in that class and vice versa */ - internal class IsConstantValueVisitor : ICustomAstVisitor + internal class IsConstantValueVisitor : ICustomAstVisitor2 { public static bool IsConstant(Ast ast, out object constantValue, bool forAttribute = false, bool forRequires = false) { @@ -130,6 +130,20 @@ public static bool IsConstant(Ast ast, out object constantValue, bool forAttribu public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) { return false; } + public object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { return false; } + + public object VisitPropertyMember(PropertyMemberAst propertyMemberAst) { return false; } + + public object VisitFunctionMember(FunctionMemberAst functionMemberAst) { return false; } + + public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { return false; } + + public object VisitUsingStatement(UsingStatementAst usingStatement) { return false; } + + public object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { return false; } + + public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) { return false; } + public object VisitStatementBlock(StatementBlockAst statementBlockAst) { if (statementBlockAst.Traps != null) return false; @@ -172,6 +186,13 @@ private static bool IsNullDivisor(ExpressionAst operand) return false; } + public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + return (bool)ternaryExpressionAst.Condition.Accept(this) && + (bool)ternaryExpressionAst.IfTrue.Accept(this) && + (bool)ternaryExpressionAst.IfFalse.Accept(this); + } + public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { return binaryExpressionAst.Operator.HasTrait(TokenFlags.CanConstantFold) && @@ -300,7 +321,7 @@ public object VisitParenExpression(ParenExpressionAst parenExpressionAst) } } - internal class ConstantValueVisitor : ICustomAstVisitor + internal class ConstantValueVisitor : ICustomAstVisitor2 { internal bool AttributeArgument { get; set; } internal bool RequiresArgument { get; set; } @@ -400,6 +421,21 @@ private static object CompileAndInvoke(Ast ast) public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) { return AutomationNull.Value; } + public object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { return AutomationNull.Value; } + + public object VisitPropertyMember(PropertyMemberAst propertyMemberAst) { return AutomationNull.Value; } + + public object VisitFunctionMember(FunctionMemberAst functionMemberAst) { return AutomationNull.Value; } + + public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { return AutomationNull.Value; } + + public object VisitUsingStatement(UsingStatementAst usingStatement) { return AutomationNull.Value; } + + public object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { return AutomationNull.Value; } + + public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) { return AutomationNull.Value; } + + public object VisitStatementBlock(StatementBlockAst statementBlockAst) { CheckIsConstant(statementBlockAst, "Caller to verify ast is constant"); @@ -412,6 +448,16 @@ public object VisitPipeline(PipelineAst pipelineAst) return pipelineAst.GetPureExpression().Accept(this); } + public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + CheckIsConstant(ternaryExpressionAst, "Caller to verify ast is constant"); + + object condition = ternaryExpressionAst.Condition.Accept(this); + return LanguagePrimitives.IsTrue(condition) + ? ternaryExpressionAst.IfTrue.Accept(this) + : ternaryExpressionAst.IfFalse.Accept(this); + } + public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { CheckIsConstant(binaryExpressionAst, "Caller to verify ast is constant"); diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index b48637cd108..b65bfb415d9 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -5824,7 +5824,7 @@ private PipelineBaseAst PipelineRule() // Skip newlines before pipe tokens to support (pipe)line continuance when pipe // tokens start the next line of script - if (nextToken.Kind == TokenKind.NewLine && _tokenizer.IsPipeContinuance(nextToken.Extent)) + if (nextToken.Kind == TokenKind.NewLine && _tokenizer.IsPipeContinuation(nextToken.Extent)) { SkipNewlines(); nextToken = PeekToken(); @@ -6426,16 +6426,127 @@ internal Ast CommandRule(bool forDynamicKeyword) #region Expressions - private ExpressionAst ExpressionRule() + /// Parse an expression. + /// + /// When it's known for sure that we are expecting an expression, allowing a generic token like '12?' or '12:' is + /// not useful. In those cases, we force to start a new token upon seeing '?' and ':' when scanning for a number + /// by setting this parameter to true, hoping to find a ternary expression. + /// + private ExpressionAst ExpressionRule(bool endNumberOnTernaryOpChars = false) { // G expression: // G logical-expression // G // G logical-expression: + // G binary-expression + // G ternary-expression + // G + // G ternary-expression: + // G binary-expression '?' new-lines:opt ternary-expression new-lines:opt ':' new-lines:opt ternary-expression + + // TODO: remove this if-block when making 'ternary operator' an official feature. + if (!ExperimentalFeature.IsEnabled("PSTernaryOperator")) + { + return BinaryExpressionRule(); + } + + RuntimeHelpers.EnsureSufficientExecutionStack(); + var oldTokenizerMode = _tokenizer.Mode; + try + { + SetTokenizerMode(TokenizerMode.Expression); + + ExpressionAst condition = BinaryExpressionRule(endNumberOnTernaryOpChars); + if (condition == null) + { + return null; + } + + Token token = PeekToken(); + + if (token.Kind != TokenKind.QuestionMark) + { + return condition; + } + + SkipToken(); + SkipNewlines(); + + // We have seen the ternary operator '?' and now expecting the 'IfFalse' expression. + ExpressionAst ifTrue = ExpressionRule(endNumberOnTernaryOpChars: true); + if (ifTrue == null) + { + // ErrorRecovery: create an error expression to fill out the ast and keep parsing. + IScriptExtent extent = After(token); + + ReportIncompleteInput( + extent, + nameof(ParserStrings.ExpectedValueExpression), + ParserStrings.ExpectedValueExpression, + token.Text); + ifTrue = new ErrorExpressionAst(extent); + } + + SkipNewlines(); + + token = NextToken(); + if (token.Kind != TokenKind.Colon) + { + var componentAsts = new List() { condition }; + + // ErrorRecovery: we have done the expression parsing and should try parsing something else. + UngetToken(token); + + // Don't bother reporting this error if we already reported an empty 'IfTrue' operand error. + if (!(ifTrue is ErrorExpressionAst)) + { + componentAsts.Add(ifTrue); + ReportIncompleteInput( + token.Extent, + nameof(ParserStrings.MissingColonInTernaryExpression), + ParserStrings.MissingColonInTernaryExpression); + } + + return new ErrorExpressionAst(ExtentOf(condition, Before(token)), componentAsts); + } + + SkipNewlines(); + + ExpressionAst ifFalse = ExpressionRule(endNumberOnTernaryOpChars: true); + if (ifFalse == null) + { + // ErrorRecovery: create an error expression to fill out the ast and keep parsing. + IScriptExtent extent = After(token); + + ReportIncompleteInput( + extent, + nameof(ParserStrings.ExpectedValueExpression), + ParserStrings.ExpectedValueExpression, + token.Text); + ifFalse = new ErrorExpressionAst(extent); + } + + return new TernaryExpressionAst(ExtentOf(condition, ifFalse), condition, ifTrue, ifFalse); + } + finally + { + SetTokenizerMode(oldTokenizerMode); + } + } + + /// Parse a binary expression. + /// + /// When it's known for sure that we are expecting an expression, allowing a generic token like '12?' or '12:' is + /// not useful. In those cases, we force to start a new token upon seeing '?' and ':' when scanning for a number + /// by setting this parameter to true, hoping to find a ternary expression. + /// + private ExpressionAst BinaryExpressionRule(bool endNumberOnTernaryOpChars = false) + { + // G binary-expression: // G bitwise-expression - // G logical-expression '-and' new-lines:opt bitwise-expression - // G logical-expression '-or' new-lines:opt bitwise-expression - // G logical-expression '-xor' new-lines:opt bitwise-expression + // G binary-expression '-and' new-lines:opt bitwise-expression + // G binary-expression '-or' new-lines:opt bitwise-expression + // G binary-expression '-xor' new-lines:opt bitwise-expression // G // G bitwise-expression: // G comparison-expression @@ -6472,7 +6583,7 @@ private ExpressionAst ExpressionRule() SetTokenizerMode(TokenizerMode.Expression); ExpressionAst lhs, rhs; - ExpressionAst expr = ArrayLiteralRule(); + ExpressionAst expr = ArrayLiteralRule(endNumberOnTernaryOpChars); if (expr == null) { @@ -6505,18 +6616,21 @@ private ExpressionAst ExpressionRule() { SkipNewlines(); - expr = ArrayLiteralRule(); + // We have seen a binary operator token and now expecting the right-hand-side expression. + expr = ArrayLiteralRule(endNumberOnTernaryOpChars: true); if (expr == null) { // ErrorRecovery: create an error expression to fill out the ast and keep parsing. - IScriptExtent extent = After(token); + // Use token.Text, not token.Kind.Text() b/c the kind might not match the actual operator used // when a case insensitive operator is used. - ReportIncompleteInput(extent, + ReportIncompleteInput( + extent, nameof(ParserStrings.ExpectedValueExpression), ParserStrings.ExpectedValueExpression, token.Text); + expr = new ErrorExpressionAst(extent); } @@ -6540,7 +6654,9 @@ private ExpressionAst ExpressionRule() Token op = operatorStack.Pop(); operandStack.Push(new BinaryExpressionAst(ExtentOf(lhs, rhs), lhs, op.Kind, rhs, op.Extent)); if (operatorStack.Count == 0) + { break; + } precedence = operatorStack.Peek().Kind.GetBinaryPrecedence(); } @@ -6587,13 +6703,23 @@ private ExpressionAst ErrorRecoveryParameterInExpression(ParameterToken paramTok new CommandParameterAst(paramToken.Extent, paramToken.ParameterName, null, paramToken.Extent)}); } - private ExpressionAst ArrayLiteralRule() + /// Parse an array literal expression. + /// + /// When it's known for sure that we are expecting an expression, allowing a generic token like '12?' or '12:' is + /// not useful. In those cases, we force to start a new token upon seeing '?' and ':' when scanning for a number + /// by setting this parameter to true, hoping to find a ternary expression. + /// + private ExpressionAst ArrayLiteralRule(bool endNumberOnTernaryOpChars = false) { // G array-literal-expression: // G unary-expression // G unary-expression ',' new-lines:opt array-literal-expression + ExpressionAst lastExpr = UnaryExpressionRule(endNumberOnTernaryOpChars); + if (lastExpr == null) + { + return null; + } - ExpressionAst lastExpr = UnaryExpressionRule(); ExpressionAst firstExpr = lastExpr; Token commaToken = PeekToken(); @@ -6609,11 +6735,11 @@ private ExpressionAst ArrayLiteralRule() SkipToken(); SkipNewlines(); - lastExpr = UnaryExpressionRule(); + // We have seen a comma token and now expecting an expression as an array element. + lastExpr = UnaryExpressionRule(endNumberOnTernaryOpChars: true); if (lastExpr == null) { // ErrorRecovery: create an error expression for the ast and break. - ReportIncompleteInput(After(commaToken), nameof(ParserStrings.MissingExpressionAfterToken), ParserStrings.MissingExpressionAfterToken, @@ -6631,7 +6757,13 @@ private ExpressionAst ArrayLiteralRule() return new ArrayLiteralAst(ExtentOf(firstExpr, lastExpr), arrayValues); } - private ExpressionAst UnaryExpressionRule() + /// Parse an unary expression. + /// + /// When it's known for sure that we are expecting an expression, allowing a generic token like '12?' or '12:' is + /// not useful. In those cases, we force to start a new token upon seeing '?' and ':' when scanning for a number + /// by setting this parameter to true, hoping to find a ternary expression. + /// + private ExpressionAst UnaryExpressionRule(bool endNumberOnTernaryOpChars = false) { // G unary-expression: // G primary-expression @@ -6662,12 +6794,27 @@ private ExpressionAst UnaryExpressionRule() ExpressionAst expr = null; Token token; bool oldAllowSignedNumbers = _tokenizer.AllowSignedNumbers; + bool oldForceEndNumberOnTernaryOperators = _tokenizer.ForceEndNumberOnTernaryOpChars; try { _tokenizer.AllowSignedNumbers = true; - if (_ungotToken != null && _ungotToken.Kind == TokenKind.Minus) + _tokenizer.ForceEndNumberOnTernaryOpChars = endNumberOnTernaryOpChars; + + if (_ungotToken != null) { - Resync(_ungotToken); + // Possibly a signed number. Need to resync. + bool needResync = _ungotToken.Kind == TokenKind.Minus; + + if (!needResync) + { + // A generic token possibly composed of numbers and ternary operator chars. Need to resync. + needResync = endNumberOnTernaryOpChars && _ungotToken.Kind == TokenKind.Generic; + } + + if (needResync) + { + Resync(_ungotToken); + } } token = PeekToken(); @@ -6675,6 +6822,7 @@ private ExpressionAst UnaryExpressionRule() finally { _tokenizer.AllowSignedNumbers = oldAllowSignedNumbers; + _tokenizer.ForceEndNumberOnTernaryOpChars = oldForceEndNumberOnTernaryOperators; } ExpressionAst child; @@ -6687,7 +6835,9 @@ private ExpressionAst UnaryExpressionRule() SkipToken(); SkipNewlines(); - child = UnaryExpressionRule(); + + // We have seen a unary operator token and now expecting an expression. + child = UnaryExpressionRule(endNumberOnTernaryOpChars: true); if (child != null) { if (token.Kind == TokenKind.Comma) @@ -6703,13 +6853,15 @@ private ExpressionAst UnaryExpressionRule() { // ErrorRecovery: don't bother constructing a unary expression, but we know we must have // some sort of expression, so return an error expression. - + // // Use token.Text, not token.Kind.Text() b/c the kind might not match the actual operator used // when a case insensitive operator is used. - ReportIncompleteInput(After(token), + ReportIncompleteInput( + After(token), nameof(ParserStrings.MissingExpressionAfterOperator), ParserStrings.MissingExpressionAfterOperator, token.Text); + return new ErrorExpressionAst(token.Extent); } } @@ -6726,16 +6878,19 @@ private ExpressionAst UnaryExpressionRule() if (lastAttribute is AttributeAst) { SkipNewlines(); - child = UnaryExpressionRule(); + + // We are now expecting a child expression. + child = UnaryExpressionRule(endNumberOnTernaryOpChars: true); if (child == null) { // ErrorRecovery: We have a list of attributes, and we know it's not before a param statement, // so we know we must have some sort of expression. Return an error expression then. - - ReportIncompleteInput(lastAttribute.Extent, + ReportIncompleteInput( + lastAttribute.Extent, nameof(ParserStrings.UnexpectedAttribute), ParserStrings.UnexpectedAttribute, lastAttribute.TypeName.FullName); + return new ErrorExpressionAst(ExtentOf(token, lastAttribute)); } @@ -6743,27 +6898,31 @@ private ExpressionAst UnaryExpressionRule() } else { - Diagnostics.Assert(_ungotToken == null || ErrorList.Count > 0, - "Unexpected lookahead from AttributeListRule."); + Diagnostics.Assert( + _ungotToken == null || ErrorList.Count > 0, + "Unexpected lookahead from AttributeListRule."); + // If we've looked ahead, don't go looking for a member access token, we've already issued an error, // just assume we're not trying to access a member. var memberAccessToken = _ungotToken != null ? null : NextMemberAccessToken(false); if (memberAccessToken != null) { - expr = CheckPostPrimaryExpressionOperators(memberAccessToken, - new TypeExpressionAst(lastAttribute.Extent, - lastAttribute.TypeName)); + expr = CheckPostPrimaryExpressionOperators( + memberAccessToken, + new TypeExpressionAst(lastAttribute.Extent, lastAttribute.TypeName)); } else { token = PeekToken(); if (token.Kind != TokenKind.NewLine && token.Kind != TokenKind.Comma) { - child = UnaryExpressionRule(); + // We are now expecting a child expression. + child = UnaryExpressionRule(endNumberOnTernaryOpChars: true); if (child != null) { - expr = new ConvertExpressionAst(ExtentOf(lastAttribute, child), - (TypeConstraintAst)lastAttribute, child); + expr = new ConvertExpressionAst( + ExtentOf(lastAttribute, child), + (TypeConstraintAst)lastAttribute, child); } } } diff --git a/src/System.Management.Automation/engine/parser/PreOrderVisitor.cs b/src/System.Management.Automation/engine/parser/PreOrderVisitor.cs index 5c7632baec9..9f0ebf5c7da 100644 --- a/src/System.Management.Automation/engine/parser/PreOrderVisitor.cs +++ b/src/System.Management.Automation/engine/parser/PreOrderVisitor.cs @@ -186,6 +186,9 @@ public abstract class AstVisitor2 : AstVisitor /// public virtual AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordStatementAst) { return AstVisitAction.Continue; } + + /// + public virtual AstVisitAction VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) { return AstVisitAction.Continue; } } /// diff --git a/src/System.Management.Automation/engine/parser/SafeValues.cs b/src/System.Management.Automation/engine/parser/SafeValues.cs index 6bc9ce874e8..75328a91ddd 100644 --- a/src/System.Management.Automation/engine/parser/SafeValues.cs +++ b/src/System.Management.Automation/engine/parser/SafeValues.cs @@ -31,12 +31,12 @@ * o VisitArrayExpression may be safe if its components are safe * o VisitArrayLiteral may be safe if its components are safe * o VisitHashtable may be safe if its components are safe - * + * o VisitTernaryExpression may be safe if its components are safe */ namespace System.Management.Automation.Language { - internal class IsSafeValueVisitor : ICustomAstVisitor + internal class IsSafeValueVisitor : ICustomAstVisitor2 { public static bool IsAstSafe(Ast ast, GetSafeValueVisitor.SafeValueContext safeValueContext) { @@ -142,6 +142,20 @@ internal bool IsAstSafe(Ast ast) public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) { return false; } + public object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { return false; } + + public object VisitPropertyMember(PropertyMemberAst propertyMemberAst) { return false; } + + public object VisitFunctionMember(FunctionMemberAst functionMemberAst) { return false; } + + public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { return false; } + + public object VisitUsingStatement(UsingStatementAst usingStatement) { return false; } + + public object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { return false; } + + public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) { return false; } + public object VisitIndexExpression(IndexExpressionAst indexExpressionAst) { return (bool)indexExpressionAst.Index.Accept(this) && (bool)indexExpressionAst.Target.Accept(this); @@ -191,6 +205,13 @@ public object VisitPipeline(PipelineAst pipelineAst) return expr != null && (bool)expr.Accept(this); } + public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + return (bool)ternaryExpressionAst.Condition.Accept(this) && + (bool)ternaryExpressionAst.IfTrue.Accept(this) && + (bool)ternaryExpressionAst.IfFalse.Accept(this); + } + public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { // This can be used for a denial of service @@ -334,7 +355,7 @@ public object VisitParenExpression(ParenExpressionAst parenExpressionAst) * except in the case of handling the unary operator * ExecutionContext is provided to ensure we can resolve variables */ - internal class GetSafeValueVisitor : ICustomAstVisitor + internal class GetSafeValueVisitor : ICustomAstVisitor2 { internal enum SafeValueContext { @@ -348,7 +369,7 @@ private GetSafeValueVisitor() { } public static object GetSafeValue(Ast ast, ExecutionContext context, SafeValueContext safeValueContext) { - s_context = context; + t_context = context; if (IsSafeValueVisitor.IsAstSafe(ast, safeValueContext)) { return ast.Accept(new GetSafeValueVisitor()); @@ -359,80 +380,98 @@ public static object GetSafeValue(Ast ast, ExecutionContext context, SafeValueCo return null; } - throw PSTraceSource.NewArgumentException("ast"); + throw PSTraceSource.NewArgumentException(nameof(ast)); } - private static ExecutionContext s_context; + /// + /// This field needs to be thread-static to make 'GetSafeValue' thread safe. + /// + [ThreadStatic] + private static ExecutionContext t_context; + + public object VisitErrorStatement(ErrorStatementAst errorStatementAst) { throw PSTraceSource.NewArgumentException(nameof(errorStatementAst)); } + + public object VisitErrorExpression(ErrorExpressionAst errorExpressionAst) { throw PSTraceSource.NewArgumentException(nameof(errorExpressionAst)); } + + public object VisitScriptBlock(ScriptBlockAst scriptBlockAst) { throw PSTraceSource.NewArgumentException(nameof(scriptBlockAst)); } + + public object VisitParamBlock(ParamBlockAst paramBlockAst) { throw PSTraceSource.NewArgumentException(nameof(paramBlockAst)); } - public object VisitErrorStatement(ErrorStatementAst errorStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitNamedBlock(NamedBlockAst namedBlockAst) { throw PSTraceSource.NewArgumentException(nameof(namedBlockAst)); } - public object VisitErrorExpression(ErrorExpressionAst errorExpressionAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitTypeConstraint(TypeConstraintAst typeConstraintAst) { throw PSTraceSource.NewArgumentException(nameof(typeConstraintAst)); } - public object VisitScriptBlock(ScriptBlockAst scriptBlockAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitAttribute(AttributeAst attributeAst) { throw PSTraceSource.NewArgumentException(nameof(attributeAst)); } - public object VisitParamBlock(ParamBlockAst paramBlockAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) { throw PSTraceSource.NewArgumentException(nameof(namedAttributeArgumentAst)); } - public object VisitNamedBlock(NamedBlockAst namedBlockAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitParameter(ParameterAst parameterAst) { throw PSTraceSource.NewArgumentException(nameof(parameterAst)); } - public object VisitTypeConstraint(TypeConstraintAst typeConstraintAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { throw PSTraceSource.NewArgumentException(nameof(functionDefinitionAst)); } - public object VisitAttribute(AttributeAst attributeAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitIfStatement(IfStatementAst ifStmtAst) { throw PSTraceSource.NewArgumentException(nameof(ifStmtAst)); } - public object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitTrap(TrapStatementAst trapStatementAst) { throw PSTraceSource.NewArgumentException(nameof(trapStatementAst)); } - public object VisitParameter(ParameterAst parameterAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitSwitchStatement(SwitchStatementAst switchStatementAst) { throw PSTraceSource.NewArgumentException(nameof(switchStatementAst)); } - public object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitDataStatement(DataStatementAst dataStatementAst) { throw PSTraceSource.NewArgumentException(nameof(dataStatementAst)); } - public object VisitIfStatement(IfStatementAst ifStmtAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitForEachStatement(ForEachStatementAst forEachStatementAst) { throw PSTraceSource.NewArgumentException(nameof(forEachStatementAst)); } - public object VisitTrap(TrapStatementAst trapStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) { throw PSTraceSource.NewArgumentException(nameof(doWhileStatementAst)); } - public object VisitSwitchStatement(SwitchStatementAst switchStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitForStatement(ForStatementAst forStatementAst) { throw PSTraceSource.NewArgumentException(nameof(forStatementAst)); } - public object VisitDataStatement(DataStatementAst dataStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitWhileStatement(WhileStatementAst whileStatementAst) { throw PSTraceSource.NewArgumentException(nameof(whileStatementAst)); } - public object VisitForEachStatement(ForEachStatementAst forEachStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitCatchClause(CatchClauseAst catchClauseAst) { throw PSTraceSource.NewArgumentException(nameof(catchClauseAst)); } - public object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitTryStatement(TryStatementAst tryStatementAst) { throw PSTraceSource.NewArgumentException(nameof(tryStatementAst)); } - public object VisitForStatement(ForStatementAst forStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitBreakStatement(BreakStatementAst breakStatementAst) { throw PSTraceSource.NewArgumentException(nameof(breakStatementAst)); } - public object VisitWhileStatement(WhileStatementAst whileStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitContinueStatement(ContinueStatementAst continueStatementAst) { throw PSTraceSource.NewArgumentException(nameof(continueStatementAst)); } - public object VisitCatchClause(CatchClauseAst catchClauseAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitReturnStatement(ReturnStatementAst returnStatementAst) { throw PSTraceSource.NewArgumentException(nameof(returnStatementAst)); } - public object VisitTryStatement(TryStatementAst tryStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitExitStatement(ExitStatementAst exitStatementAst) { throw PSTraceSource.NewArgumentException(nameof(exitStatementAst)); } - public object VisitBreakStatement(BreakStatementAst breakStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitThrowStatement(ThrowStatementAst throwStatementAst) { throw PSTraceSource.NewArgumentException(nameof(throwStatementAst)); } - public object VisitContinueStatement(ContinueStatementAst continueStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) { throw PSTraceSource.NewArgumentException(nameof(doUntilStatementAst)); } - public object VisitReturnStatement(ReturnStatementAst returnStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { throw PSTraceSource.NewArgumentException(nameof(assignmentStatementAst)); } - public object VisitExitStatement(ExitStatementAst exitStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitCommand(CommandAst commandAst) { throw PSTraceSource.NewArgumentException(nameof(commandAst)); } - public object VisitThrowStatement(ThrowStatementAst throwStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitCommandExpression(CommandExpressionAst commandExpressionAst) { throw PSTraceSource.NewArgumentException(nameof(commandExpressionAst)); } - public object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitCommandParameter(CommandParameterAst commandParameterAst) { throw PSTraceSource.NewArgumentException(nameof(commandParameterAst)); } - public object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitFileRedirection(FileRedirectionAst fileRedirectionAst) { throw PSTraceSource.NewArgumentException(nameof(fileRedirectionAst)); } - public object VisitCommand(CommandAst commandAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) { throw PSTraceSource.NewArgumentException(nameof(mergingRedirectionAst)); } - public object VisitCommandExpression(CommandExpressionAst commandExpressionAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) { throw PSTraceSource.NewArgumentException(nameof(attributedExpressionAst)); } - public object VisitCommandParameter(CommandParameterAst commandParameterAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitBlockStatement(BlockStatementAst blockStatementAst) { throw PSTraceSource.NewArgumentException(nameof(blockStatementAst)); } - public object VisitFileRedirection(FileRedirectionAst fileRedirectionAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) { throw PSTraceSource.NewArgumentException(nameof(invokeMemberExpressionAst)); } - public object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { throw PSTraceSource.NewArgumentException(nameof(typeDefinitionAst)); } - public object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitPropertyMember(PropertyMemberAst propertyMemberAst) { throw PSTraceSource.NewArgumentException(nameof(propertyMemberAst)); } - public object VisitBlockStatement(BlockStatementAst blockStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitFunctionMember(FunctionMemberAst functionMemberAst) { throw PSTraceSource.NewArgumentException(nameof(functionMemberAst)); } - public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) { throw PSTraceSource.NewArgumentException("ast"); } + public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { throw PSTraceSource.NewArgumentException(nameof(baseCtorInvokeMemberExpressionAst)); } + + public object VisitUsingStatement(UsingStatementAst usingStatement) { throw PSTraceSource.NewArgumentException(nameof(usingStatement)); } + + public object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { throw PSTraceSource.NewArgumentException(nameof(configurationDefinitionAst)); } + + public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) { throw PSTraceSource.NewArgumentException(nameof(dynamicKeywordAst)); } // // This is similar to logic used deep in the engine for slicing something that can be sliced @@ -501,9 +540,9 @@ public object VisitExpandableStringExpression(ExpandableStringExpressionAst expa object[] safeValues = new object[expandableStringExpressionAst.NestedExpressions.Count]; // retrieve OFS, and if it doesn't exist set it to space string ofs = null; - if (s_context != null) + if (t_context != null) { - ofs = s_context.SessionState.PSVariable.GetValue("OFS") as string; + ofs = t_context.SessionState.PSVariable.GetValue("OFS") as string; } if (ofs == null) @@ -591,7 +630,7 @@ public object VisitStatementBlock(StatementBlockAst statementBlockAst) } else { - throw PSTraceSource.NewArgumentException("ast"); + throw PSTraceSource.NewArgumentException(nameof(statementBlockAst)); } } @@ -606,7 +645,17 @@ public object VisitPipeline(PipelineAst pipelineAst) return expr.Accept(this); } - throw PSTraceSource.NewArgumentException("ast"); + throw PSTraceSource.NewArgumentException(nameof(pipelineAst)); + } + + public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + if (t_context == null) + { + throw PSTraceSource.NewArgumentException(nameof(ternaryExpressionAst)); + } + + return Compiler.GetExpressionValue(ternaryExpressionAst, isTrustedInput: true, t_context, usingValues: null); } public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) @@ -614,33 +663,29 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) // This can be used for a denial of service // Write-Output (((((("AAAAAAAAAAAAAAAAAAAAAA"*2)*2)*2)*2)*2)*2) // Keep on going with that pattern, and we're generating gigabytes of strings. - throw PSTraceSource.NewArgumentException("ast"); + throw PSTraceSource.NewArgumentException(nameof(binaryExpressionAst)); } public object VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) { - if (s_context != null) - { - return Compiler.GetExpressionValue(unaryExpressionAst, true, s_context, null); - } - else + if (t_context == null) { - throw PSTraceSource.NewArgumentException("ast"); + throw PSTraceSource.NewArgumentException(nameof(unaryExpressionAst)); } + + return Compiler.GetExpressionValue(unaryExpressionAst, isTrustedInput: true, t_context, usingValues: null); } public object VisitConvertExpression(ConvertExpressionAst convertExpressionAst) { // at this point, we know we're safe because we checked both the type and the child, // so now we can just call the compiler and indicate that it's trusted (at this point) - if (s_context != null) + if (t_context == null) { - return Compiler.GetExpressionValue(convertExpressionAst, true, s_context, null); - } - else - { - throw PSTraceSource.NewArgumentException("ast"); + throw PSTraceSource.NewArgumentException(nameof(convertExpressionAst)); } + + return Compiler.GetExpressionValue(convertExpressionAst, isTrustedInput: true, t_context, usingValues: null); } public object VisitConstantExpression(ConstantExpressionAst constantExpressionAst) @@ -691,12 +736,12 @@ public object VisitVariableExpression(VariableExpressionAst variableExpressionAs return Path.GetDirectoryName(scriptFileName); } - if (s_context != null) + if (t_context != null) { - return VariableOps.GetVariableValue(variableExpressionAst.VariablePath, s_context, variableExpressionAst); + return VariableOps.GetVariableValue(variableExpressionAst.VariablePath, t_context, variableExpressionAst); } - throw PSTraceSource.NewArgumentException("ast"); + throw PSTraceSource.NewArgumentException(nameof(variableExpressionAst)); } public object VisitTypeExpression(TypeExpressionAst typeExpressionAst) @@ -704,12 +749,12 @@ public object VisitTypeExpression(TypeExpressionAst typeExpressionAst) // Type expressions are not safe as they allow fingerprinting by providing // a set of types, you can inspect the types in the AppDomain implying which assemblies are in use // and their version - throw PSTraceSource.NewArgumentException("ast"); + throw PSTraceSource.NewArgumentException(nameof(typeExpressionAst)); } public object VisitMemberExpression(MemberExpressionAst memberExpressionAst) { - throw PSTraceSource.NewArgumentException("ast"); + throw PSTraceSource.NewArgumentException(nameof(memberExpressionAst)); } public object VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) diff --git a/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs index 8739ddfdbb1..bcc504bbde0 100644 --- a/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs +++ b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs @@ -2326,6 +2326,11 @@ object ICustomAstVisitor2.VisitDynamicKeywordStatement(DynamicKeywordStatementAs return dynamicKeywordAst.CommandElements[0].Accept(this); } + object ICustomAstVisitor2.VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + return InferTypes(ternaryExpressionAst.IfTrue).Concat(InferTypes(ternaryExpressionAst.IfFalse)); + } + private static CommandBaseAst GetPreviousPipelineCommand(CommandAst commandAst) { var pipe = (PipelineAst)commandAst.Parent; diff --git a/src/System.Management.Automation/engine/parser/VariableAnalysis.cs b/src/System.Management.Automation/engine/parser/VariableAnalysis.cs index f4afade448c..66d7f43804d 100644 --- a/src/System.Management.Automation/engine/parser/VariableAnalysis.cs +++ b/src/System.Management.Automation/engine/parser/VariableAnalysis.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Reflection; namespace System.Management.Automation.Language { @@ -191,6 +190,25 @@ private void VisitParameters(ReadOnlyCollection parameters) } } + // Add a variable to the variable dictionary + private void NoteVariable(string variableName, int index, Type type, bool automatic = false, bool preferenceVariable = false) + { + if (!_variables.ContainsKey(variableName)) + { + var details = new VariableAnalysisDetails + { + BitIndex = _variables.Count, + LocalTupleIndex = index, + Name = variableName, + Type = type, + Automatic = automatic, + PreferenceVariable = preferenceVariable, + Assigned = false, + }; + _variables.Add(variableName, details); + } + } + public override AstVisitAction VisitDataStatement(DataStatementAst dataStatementAst) { if (dataStatementAst.Variable != null) @@ -293,28 +311,9 @@ public override AstVisitAction VisitTrap(TrapStatementAst trapStatementAst) // We don't want to discover any variables in traps - they get their own scope. return AstVisitAction.SkipChildren; } - - // Add a variable to the variable dictionary - private void NoteVariable(string variableName, int index, Type type, bool automatic = false, bool preferenceVariable = false) - { - if (!_variables.ContainsKey(variableName)) - { - var details = new VariableAnalysisDetails - { - BitIndex = _variables.Count, - LocalTupleIndex = index, - Name = variableName, - Type = type, - Automatic = automatic, - PreferenceVariable = preferenceVariable, - Assigned = false, - }; - _variables.Add(variableName, details); - } - } } - internal class VariableAnalysis : ICustomAstVisitor + internal class VariableAnalysis : ICustomAstVisitor2 { // Tuple slots start at index 0. >= 0 means a variable is allocated in the tuple. -1 means we haven't // analyzed a specific use of a variable and don't know what slot it might be assigned to yet. @@ -1060,6 +1059,28 @@ public object VisitIfStatement(IfStatementAst ifStmtAst) return null; } + public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + var ifTrue = new Block(); + var ifFalse = new Block(); + var after = new Block(); + + ternaryExpressionAst.Condition.Accept(this); + _currentBlock.FlowsTo(ifTrue); + _currentBlock.FlowsTo(ifFalse); + _currentBlock = ifTrue; + + ternaryExpressionAst.IfTrue.Accept(this); + _currentBlock.FlowsTo(after); + _currentBlock = ifFalse; + + ternaryExpressionAst.IfFalse.Accept(this); + _currentBlock.FlowsTo(after); + _currentBlock = after; + + return null; + } + public object VisitTrap(TrapStatementAst trapStatementAst) { trapStatementAst.Body.Accept(this); @@ -1639,7 +1660,7 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) // The right operand is conditionally evaluated. We aren't generating any code here, just // modeling the flow graph, so we just visit the right operand in a new block, and have - // both the current and new blocks both flow to a post-expression block. + // both the current and new blocks flow to a post-expression block. var targetBlock = new Block(); var nextBlock = new Block(); _currentBlock.FlowsTo(targetBlock); @@ -1823,5 +1844,19 @@ public object VisitBlockStatement(BlockStatementAst blockStatementAst) blockStatementAst.Body.Accept(this); return null; } + + public object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) => null; + + public object VisitPropertyMember(PropertyMemberAst propertyMemberAst) => null; + + public object VisitFunctionMember(FunctionMemberAst functionMemberAst) => null; + + public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) => null; + + public object VisitUsingStatement(UsingStatementAst usingStatement) => null; + + public object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) => null; + + public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) => null; } } diff --git a/src/System.Management.Automation/engine/parser/ast.cs b/src/System.Management.Automation/engine/parser/ast.cs index 434b5a6b5e6..5c10509d38f 100644 --- a/src/System.Management.Automation/engine/parser/ast.cs +++ b/src/System.Management.Automation/engine/parser/ast.cs @@ -7098,13 +7098,111 @@ internal virtual bool ShouldPreserveOutputInCaseOfException() } } + /// + /// The ast representing a ternary expression, e.g. $a ? 1 : 2. + /// + public class TernaryExpressionAst : ExpressionAst + { + /// + /// Initializes a new instance of the a ternary expression. + /// + /// The extent of the expression. + /// The condition operand. + /// The if clause. + /// The else clause. + public TernaryExpressionAst(IScriptExtent extent, ExpressionAst condition, ExpressionAst ifTrue, ExpressionAst ifFalse) + : base(extent) + { + Condition = condition ?? throw PSTraceSource.NewArgumentNullException(nameof(condition)); + IfTrue = ifTrue ?? throw PSTraceSource.NewArgumentNullException(nameof(ifTrue)); + IfFalse = ifFalse ?? throw PSTraceSource.NewArgumentNullException(nameof(ifFalse)); + + SetParent(Condition); + SetParent(IfTrue); + SetParent(IfFalse); + } + + /// + /// Gets the ast for the condition of the ternary expression. The property is never null. + /// + public ExpressionAst Condition { get; } + + /// + /// Gets the ast for the if-operand of the ternary expression. The property is never null. + /// + public ExpressionAst IfTrue { get; } + + /// + /// Gets the ast for the else-operand of the ternary expression. The property is never null. + /// + public ExpressionAst IfFalse { get; } + + /// + /// Copy the TernaryExpressionAst instance. + /// + /// + /// Retirns a copy of the ast. + /// + public override Ast Copy() + { + ExpressionAst newCondition = CopyElement(this.Condition); + ExpressionAst newIfTrue = CopyElement(this.IfTrue); + ExpressionAst newIfFalse = CopyElement(this.IfFalse); + return new TernaryExpressionAst(this.Extent, newCondition, newIfTrue, newIfFalse); + } + + #region Visitors + + internal override object Accept(ICustomAstVisitor visitor) + { + if (visitor is ICustomAstVisitor2 visitor2) + { + return visitor2.VisitTernaryExpression(this); + } + + return null; + } + + internal override AstVisitAction InternalVisit(AstVisitor visitor) + { + var action = AstVisitAction.Continue; + if (visitor is AstVisitor2 visitor2) + { + action = visitor2.VisitTernaryExpression(this); + if (action == AstVisitAction.SkipChildren) + { + return visitor.CheckForPostAction(this, AstVisitAction.Continue); + } + } + + if (action == AstVisitAction.Continue) + { + action = Condition.InternalVisit(visitor); + } + + if (action == AstVisitAction.Continue) + { + action = IfTrue.InternalVisit(visitor); + } + + if (action == AstVisitAction.Continue) + { + action = IfFalse.InternalVisit(visitor); + } + + return visitor.CheckForPostAction(this, action); + } + + #endregion Visitors + } + /// /// The ast representing a binary expression, e.g. $a + $b. /// public class BinaryExpressionAst : ExpressionAst { /// - /// Construct a binary expression. + /// Initializes a new instance of the binary expression. /// /// The extent of the expression. /// The left hand operand. diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index def6b8d0dc1..d11a5963dd1 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -413,6 +413,9 @@ public enum TokenKind /// The PS class base class and implemented interfaces operator ':'. Also used in base class ctor calls. Colon = 99, + /// The ternary operator '?'. + QuestionMark = 100, + #endregion Operators #region Keywords @@ -655,7 +658,10 @@ public enum TokenFlags /// CaseSensitiveOperator = 0x00000400, - // Unused = 0x00000800, + /// + /// The token is a ternary operator '?'. + /// + TernaryOperator = 0x00000800, /// /// The operators '&', '|', and the member access operators ':' and '::'. @@ -847,7 +853,7 @@ public static class TokenTraits /* Shl */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, /* Shr */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, /* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, - /* Reserved slot 2 */ TokenFlags.None, + /* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode, /* Reserved slot 3 */ TokenFlags.None, /* Reserved slot 4 */ TokenFlags.None, /* Reserved slot 5 */ TokenFlags.None, diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index 328417a8eae..6f56afdb9c0 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -721,6 +721,15 @@ internal Tokenizer(Parser parser) internal TokenizerMode Mode { get; set; } internal bool AllowSignedNumbers { get; set; } + + // TODO: use auto-properties when making 'ternary operator' an official feature. + private bool _forceEndNumberOnTernaryOpChars; + internal bool ForceEndNumberOnTernaryOpChars + { + get { return _forceEndNumberOnTernaryOpChars; } + set { _forceEndNumberOnTernaryOpChars = value && ExperimentalFeature.IsEnabled("PSTernaryOperator"); } + } + internal bool WantSimpleName { get; set; } internal bool InWorkflowContext { get; set; } internal List TokenList { get; set; } @@ -1342,15 +1351,15 @@ private bool OnlyWhitespaceOrCommentsAfterExtent(InternalScriptExtent extent) return true; } - internal bool IsPipeContinuance(IScriptExtent extent) + internal bool IsPipeContinuation(IScriptExtent extent) { - return extent.EndOffset < _script.Length && PipeContinuanceAfterExtent(extent); + // If the first non-whitespace & non-comment (regular or block) character following a newline is a pipe, we have + // pipe continuation. + return extent.EndOffset < _script.Length && ContinuationAfterExtent(extent, continuationChar: '|'); } - private bool PipeContinuanceAfterExtent(IScriptExtent extent) + private bool ContinuationAfterExtent(IScriptExtent extent, char continuationChar) { - // If the first non-comment (regular or block) character following a newline is a pipe, we have - // pipe continuance. bool lastNonWhitespaceIsNewline = true; int i = extent.EndOffset; @@ -1372,7 +1381,7 @@ private bool PipeContinuanceAfterExtent(IScriptExtent extent) { if (lastNonWhitespaceIsNewline) { - // blank or whitespace-only lines are not allowed in automatic line continuance + // blank or whitespace-only lines are not allowed in automatic line continuation return false; } @@ -1384,7 +1393,7 @@ private bool PipeContinuanceAfterExtent(IScriptExtent extent) { if (lastNonWhitespaceIsNewline) { - // blank or whitespace-only lines are not allowed in automatic line continuance + // blank or whitespace-only lines are not allowed in automatic line continuation return false; } @@ -1409,10 +1418,10 @@ private bool PipeContinuanceAfterExtent(IScriptExtent extent) continue; } - return c == '|'; + return c == continuationChar; } - return _script[_script.Length - 1] == '|'; + return _script[_script.Length - 1] == continuationChar; } private int SkipLineComment(int i) @@ -3858,7 +3867,7 @@ private Token ScanNumber(char firstChar) firstChar == '.' || (firstChar >= '0' && firstChar <= '9') || (AllowSignedNumbers && (firstChar == '+' || firstChar.IsDash())), "Number must start with '.', '-', or digit."); - ReadOnlySpan strNum = ScanNumberHelper(firstChar, out NumberFormat format, out NumberSuffixFlags suffix, out bool real, out long multiplier); + string strNum = ScanNumberHelper(firstChar, out NumberFormat format, out NumberSuffixFlags suffix, out bool real, out long multiplier); // the token is not a number. i.e. 77z.exe if (strNum == null) @@ -3900,7 +3909,7 @@ private Token ScanNumber(char firstChar) /// OR /// Return the string format of the number. /// - private ReadOnlySpan ScanNumberHelper(char firstChar, out NumberFormat format, out NumberSuffixFlags suffix, out bool real, out long multiplier) + private string ScanNumberHelper(char firstChar, out NumberFormat format, out NumberSuffixFlags suffix, out bool real, out long multiplier) { format = NumberFormat.Decimal; suffix = NumberSuffixFlags.None; @@ -4106,7 +4115,7 @@ private ReadOnlySpan ScanNumberHelper(char firstChar, out NumberFormat for if (!c.ForceStartNewToken()) { - if (!InExpressionMode() || !c.ForceStartNewTokenAfterNumber()) + if (!InExpressionMode() || !c.ForceStartNewTokenAfterNumber(ForceEndNumberOnTernaryOpChars)) { notNumber = true; } @@ -4129,7 +4138,7 @@ private ReadOnlySpan ScanNumberHelper(char firstChar, out NumberFormat for sb[0] = '-'; } - return GetStringAndRelease(sb).AsSpan(); + return GetStringAndRelease(sb); } #endregion Numbers @@ -4953,7 +4962,7 @@ internal Token NextToken() if (InExpressionMode() && (char.IsDigit(c1) || c1 == '.')) { // check if the next token is actually a number - ReadOnlySpan strNum = ScanNumberHelper(c, out NumberFormat format, out NumberSuffixFlags suffix, out bool real, out long multiplier); + string strNum = ScanNumberHelper(c, out _, out _, out _, out _); // rescan characters after the check _currentIndex = _tokenStart; c = GetChar(); @@ -4984,6 +4993,9 @@ internal Token NextToken() return this.NewToken(TokenKind.Colon); + case '?' when InExpressionMode(): + return this.NewToken(TokenKind.QuestionMark); + case '\0': if (AtEof()) { diff --git a/src/System.Management.Automation/engine/remoting/client/Job.cs b/src/System.Management.Automation/engine/remoting/client/Job.cs index 8331f120858..18a5094228a 100644 --- a/src/System.Management.Automation/engine/remoting/client/Job.cs +++ b/src/System.Management.Automation/engine/remoting/client/Job.cs @@ -3909,28 +3909,30 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC return _wrappedDebugger.ProcessCommand(command, output); } - /// - /// Adds the provided set of breakpoints to the debugger. - /// - /// Breakpoints. - public override void SetBreakpoints(IEnumerable breakpoints) - { - _wrappedDebugger.SetBreakpoints(breakpoints); - } - - /// - /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. - /// - /// Id of the breakpoint you want. public override Breakpoint GetBreakpoint(int id) => _wrappedDebugger.GetBreakpoint(id); - /// - /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. - /// public override List GetBreakpoints() => _wrappedDebugger.GetBreakpoints(); + public override CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action = null, string path = null) => + _wrappedDebugger.SetCommandBreakpoint(command, action, path); + + public override LineBreakpoint SetLineBreakpoint(string path, int line, int column = 0, ScriptBlock action = null) => + _wrappedDebugger.SetLineBreakpoint(path, line, column, action); + + public override VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode = VariableAccessMode.Write, ScriptBlock action = null, string path = null) => + _wrappedDebugger.SetVariableBreakpoint(variableName, accessMode, action, path); + + public override bool RemoveBreakpoint(Breakpoint breakpoint) => + _wrappedDebugger.RemoveBreakpoint(breakpoint); + + public override Breakpoint EnableBreakpoint(Breakpoint breakpoint) => + _wrappedDebugger.EnableBreakpoint(breakpoint); + + public override Breakpoint DisableBreakpoint(Breakpoint breakpoint) => + _wrappedDebugger.DisableBreakpoint(breakpoint); + /// /// Sets the debugger resume action. /// diff --git a/src/System.Management.Automation/engine/remoting/client/JobManager.cs b/src/System.Management.Automation/engine/remoting/client/JobManager.cs index 8585d296007..a81c5efdeff 100644 --- a/src/System.Management.Automation/engine/remoting/client/JobManager.cs +++ b/src/System.Management.Automation/engine/remoting/client/JobManager.cs @@ -81,51 +81,43 @@ internal void RegisterJobSourceAdapter(Type jobSourceAdapterType) Dbg.Assert(jobSourceAdapterType != null, "JobSourceAdapterType should never be called with null value."); object instance = null; - if (jobSourceAdapterType.FullName != null && jobSourceAdapterType.FullName.EndsWith("WorkflowJobSourceAdapter", StringComparison.OrdinalIgnoreCase)) + ConstructorInfo constructor = jobSourceAdapterType.GetConstructor(Type.EmptyTypes); + if (!constructor.IsPublic) { - MethodInfo method = jobSourceAdapterType.GetMethod("GetInstance"); - instance = method.Invoke(null, null); + string message = string.Format(CultureInfo.CurrentCulture, + RemotingErrorIdStrings.JobManagerRegistrationConstructorError, + jobSourceAdapterType.FullName); + throw new InvalidOperationException(message); } - else - { - ConstructorInfo constructor = jobSourceAdapterType.GetConstructor(Type.EmptyTypes); - if (!constructor.IsPublic) - { - string message = string.Format(CultureInfo.CurrentCulture, - RemotingErrorIdStrings.JobManagerRegistrationConstructorError, - jobSourceAdapterType.FullName); - throw new InvalidOperationException(message); - } - try - { - instance = constructor.Invoke(null); - } - catch (MemberAccessException exception) - { - _tracer.TraceException(exception); - throw; - } - catch (TargetInvocationException exception) - { - _tracer.TraceException(exception); - throw; - } - catch (TargetParameterCountException exception) - { - _tracer.TraceException(exception); - throw; - } - catch (NotSupportedException exception) - { - _tracer.TraceException(exception); - throw; - } - catch (SecurityException exception) - { - _tracer.TraceException(exception); - throw; - } + try + { + instance = constructor.Invoke(null); + } + catch (MemberAccessException exception) + { + _tracer.TraceException(exception); + throw; + } + catch (TargetInvocationException exception) + { + _tracer.TraceException(exception); + throw; + } + catch (TargetParameterCountException exception) + { + _tracer.TraceException(exception); + throw; + } + catch (NotSupportedException exception) + { + _tracer.TraceException(exception); + throw; + } + catch (SecurityException exception) + { + _tracer.TraceException(exception); + throw; } if (instance != null) diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs index 60f101d89f0..594ab4a5256 100644 --- a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs +++ b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs @@ -32,7 +32,7 @@ internal class RemoteRunspace : Runspace, IDisposable { #region Private Members - private ArrayList _runningPipelines = new ArrayList(); + private List _runningPipelines = new List(); private object _syncRoot = new object(); private RunspaceStateInfo _runspaceStateInfo = new RunspaceStateInfo(RunspaceState.BeforeOpen); private bool _bSessionStateProxyCallInProgress = false; @@ -1522,7 +1522,7 @@ private bool WaitForFinishofPipelines() lock (_syncRoot) { - runningPipelines = (RemotePipeline[])_runningPipelines.ToArray(typeof(RemotePipeline)); + runningPipelines = _runningPipelines.ToArray(); } if (runningPipelines.Length > 0) @@ -1809,7 +1809,8 @@ internal sealed class RemoteDebugger : Debugger, IDisposable private bool _remoteDebugSupported; private bool _isActive; private int _breakpointCount; - private Version _serverPSVersion; + private RemoteDebuggingCapability _remoteDebuggingCapability; + private bool? _remoteBreakpointManagementIsSupported; private volatile bool _handleDebuggerStop; private bool _isDebuggerSteppingEnabled; private UnhandledBreakpointProcessingMode _unhandledBreakpointMode; @@ -2005,27 +2006,165 @@ public override void StopProcessCommand() } } - /// - /// Adds the provided set of breakpoints to the debugger. - /// - /// Breakpoints. - public override void SetBreakpoints(IEnumerable breakpoints) - { - _runspace.Debugger?.SetBreakpoints(breakpoints); - } - /// /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. /// /// Id of the breakpoint you want. - public override Breakpoint GetBreakpoint(int id) => - _runspace.Debugger?.GetBreakpoint(id); + public override Breakpoint GetBreakpoint(int id) + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.GetBreakpoint); + + return InvokeRemoteBreakpointFunction( + RemoteDebuggingCommands.GetBreakpoint, + new Dictionary + { + { "Id", id }, + }); + } /// /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. /// - public override List GetBreakpoints() => - _runspace.Debugger?.GetBreakpoints(); + public override List GetBreakpoints() + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.GetBreakpoint); + + CheckForValidateState(); + + var breakpoints = new List(); + + using (PowerShell ps = GetNestedPowerShell()) + { + ps.AddCommand(RemoteDebuggingCommands.GetBreakpoint); + + Collection output = ps.Invoke(); + foreach (var item in output) + { + if (item?.BaseObject is Breakpoint bp) + { + breakpoints.Add(bp); + } + } + } + + return breakpoints; + } + + public override CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action = null, string path = null) + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.SetBreakpoint); + + var functionParameters = new Dictionary + { + { "Command", command }, + }; + + if (action != null) + { + functionParameters.Add("Action", action); + } + + if (path != null) + { + functionParameters.Add("Script", path); + } + + return InvokeRemoteBreakpointFunction(RemoteDebuggingCommands.SetBreakpoint, functionParameters); + } + + public override LineBreakpoint SetLineBreakpoint(string path, int line, int column = 0, ScriptBlock action = null) + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.SetBreakpoint); + + var functionParameters = new Dictionary + { + { "Script", path }, + { "Line", line }, + }; + + if (column != 0) + { + functionParameters.Add("Column", column); + } + + if (action != null) + { + functionParameters.Add("Action", action); + } + + return InvokeRemoteBreakpointFunction(RemoteDebuggingCommands.SetBreakpoint, functionParameters); + } + + public override VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode = VariableAccessMode.Write, ScriptBlock action = null, string path = null) + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.SetBreakpoint); + + var functionParameters = new Dictionary + { + { "Variable", variableName }, + }; + + if (accessMode != VariableAccessMode.Write) + { + functionParameters.Add("Mode", accessMode); + } + + if (action != null) + { + functionParameters.Add("Action", action); + } + + if (path != null) + { + functionParameters.Add("Script", path); + } + + return InvokeRemoteBreakpointFunction(RemoteDebuggingCommands.SetBreakpoint, functionParameters); + } + + public override bool RemoveBreakpoint(Breakpoint breakpoint) + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.RemoveBreakpoint); + + return InvokeRemoteBreakpointFunction( + RemoteDebuggingCommands.RemoveBreakpoint, + new Dictionary + { + { "Id", breakpoint.Id }, + }); + } + + public override Breakpoint EnableBreakpoint(Breakpoint breakpoint) + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.EnableBreakpoint); + + return InvokeRemoteBreakpointFunction( + RemoteDebuggingCommands.EnableBreakpoint, + new Dictionary + { + { "Id", breakpoint.Id }, + }); + } + + public override Breakpoint DisableBreakpoint(Breakpoint breakpoint) + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.DisableBreakpoint); + + return InvokeRemoteBreakpointFunction( + RemoteDebuggingCommands.DisableBreakpoint, + new Dictionary + { + { "Id", breakpoint.Id }, + }); + } /// /// SetDebuggerAction. @@ -2039,7 +2178,7 @@ public override void SetDebuggerAction(DebuggerResumeAction resumeAction) using (PowerShell ps = GetNestedPowerShell()) { - ps.AddCommand(DebuggerUtils.SetDebuggerActionFunctionName).AddParameter("ResumeAction", resumeAction); + ps.AddCommand(RemoteDebuggingCommands.SetDebuggerAction).AddParameter("ResumeAction", resumeAction); ps.Invoke(); // If an error exception is returned then throw it here. @@ -2065,7 +2204,7 @@ public override DebuggerStopEventArgs GetDebuggerStopArgs() { using (PowerShell ps = GetNestedPowerShell()) { - ps.AddCommand(DebuggerUtils.GetDebuggerStopArgsFunctionName); + ps.AddCommand(RemoteDebuggingCommands.GetDebuggerStopArgs); Collection output = ps.Invoke(); foreach (var item in output) { @@ -2102,7 +2241,7 @@ public override void SetDebugMode(DebugModes mode) using (PowerShell ps = GetNestedPowerShell()) { ps.SetIsNested(false); - ps.AddCommand(DebuggerUtils.SetDebugModeFunctionName).AddParameter("Mode", mode); + ps.AddCommand(RemoteDebuggingCommands.SetDebugMode).AddParameter("Mode", mode); ps.Invoke(); } @@ -2120,8 +2259,7 @@ public override void SetDebuggerStepMode(bool enabled) CheckForValidateState(); // This is supported only for PowerShell versions >= 5.0 - if ((_serverPSVersion == null) || - (_serverPSVersion.Major < PSVersionInfo.PSV5Version.Major)) + if (!_remoteDebuggingCapability.IsCommandSupported(RemoteDebuggingCommands.SetDebuggerStepMode)) { return; } @@ -2134,7 +2272,7 @@ public override void SetDebuggerStepMode(bool enabled) // Send Enable-DebuggerStepping virtual command. using (PowerShell ps = GetNestedPowerShell()) { - ps.AddCommand(DebuggerUtils.SetDebuggerStepMode).AddParameter("Enabled", enabled); + ps.AddCommand(RemoteDebuggingCommands.SetDebuggerStepMode).AddParameter("Enabled", enabled); ps.Invoke(); _isDebuggerSteppingEnabled = enabled; } @@ -2202,8 +2340,7 @@ internal override UnhandledBreakpointProcessingMode UnhandledBreakpointMode CheckForValidateState(); // This is supported only for PowerShell versions >= 5.0 - if ((_serverPSVersion == null) || - (_serverPSVersion < PSVersionInfo.PSV5Version)) + if (!_remoteDebuggingCapability.IsCommandSupported(RemoteDebuggingCommands.SetUnhandledBreakpointMode)) { return; } @@ -2213,7 +2350,7 @@ internal override UnhandledBreakpointProcessingMode UnhandledBreakpointMode // Send Set-PSUnhandledBreakpointMode virtual command. using (PowerShell ps = GetNestedPowerShell()) { - ps.AddCommand(DebuggerUtils.SetPSUnhandledBreakpointMode).AddParameter("UnhandledBreakpointMode", value); + ps.AddCommand(RemoteDebuggingCommands.SetUnhandledBreakpointMode).AddParameter("UnhandledBreakpointMode", value); ps.Invoke(); } @@ -2303,7 +2440,7 @@ internal void SetClientDebugInfo( SetRemoteDebug(true, RunspaceAvailability.RemoteDebug); } - _serverPSVersion = serverPSVersion; + _remoteDebuggingCapability = RemoteDebuggingCapability.CreateDebuggingCapability(serverPSVersion); _breakpointCount = breakpointCount; _isDebuggerSteppingEnabled = breakAll; @@ -2610,6 +2747,59 @@ private void SetIsActive(int breakpointCount) } } + private T InvokeRemoteBreakpointFunction(string functionName, Dictionary parameters) + { + CheckForValidateState(); + + using (PowerShell ps = GetNestedPowerShell()) + { + ps.AddCommand(functionName); + foreach (var parameterName in parameters.Keys) + { + ps.AddParameter(parameterName, parameters[parameterName]); + } + + Collection output = ps.Invoke(); + + // If an error exception is returned then throw it here. + if (ps.ErrorBuffer.Count > 0) + { + Exception e = ps.ErrorBuffer[0].Exception; + if (e != null) + { + throw e; + } + } + + // This helper is only used to return a single output item of type T. + foreach (var item in output) + { + if (item?.BaseObject is T) + { + return (T)item.BaseObject; + } + } + + return default(T); + } + } + + private void CheckRemoteBreakpointManagementSupport(string breakpointCommandNameToCheck) + { + if (_remoteBreakpointManagementIsSupported == null) + { + _remoteBreakpointManagementIsSupported = _remoteDebuggingCapability.IsCommandSupported(breakpointCommandNameToCheck); + } + + if (!_remoteBreakpointManagementIsSupported.Value) + { + throw new PSNotSupportedException( + StringUtil.Format( + DebuggerStrings.CommandNotSupportedForRemoteUseInServerDebugger, + RemoteDebuggingCommands.CleanCommandName(breakpointCommandNameToCheck))); + } + } + #endregion } diff --git a/src/System.Management.Automation/engine/remoting/commands/DebugJob.cs b/src/System.Management.Automation/engine/remoting/commands/DebugJob.cs index 954bf415f19..385182fc12f 100644 --- a/src/System.Management.Automation/engine/remoting/commands/DebugJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/DebugJob.cs @@ -97,6 +97,13 @@ public Guid InstanceId set; } + /// + /// Gets or sets a flag that tells PowerShell to automatically perform a BreakAll when the debugger is attached to the remote target. + /// + [Experimental("Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace", ExperimentAction.Show)] + [Parameter] + public SwitchParameter BreakAll { get; set; } + #endregion #region Overrides @@ -177,7 +184,7 @@ protected override void EndProcessing() // Set up host script debugger to debug the job. _debugger = runspace.Debugger; - _debugger.DebugJob(_job); + _debugger.DebugJob(_job, breakAll: BreakAll); // Blocking call. Send job output to host UI while debugging and wait for Job completion. WaitAndReceiveJobOutput(); diff --git a/src/System.Management.Automation/engine/remoting/commands/StartJob.cs b/src/System.Management.Automation/engine/remoting/commands/StartJob.cs index 0f5cc64ebe9..615d6e9bd30 100644 --- a/src/System.Management.Automation/engine/remoting/commands/StartJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/StartJob.cs @@ -480,6 +480,13 @@ public virtual ScriptBlock InitializationScript private ScriptBlock _initScript; + /// + /// Gets or sets an initial working directory for the powershell background job. + /// + [Parameter] + [ValidateNotNullOrEmpty] + public string WorkingDirectory { get; set; } + /// /// Launches the background job as a 32-bit process. This can be used on /// 64-bit systems to launch a 32-bit wow process for the background job. @@ -589,6 +596,18 @@ protected override void BeginProcessing() ThrowTerminatingError(errorRecord); } + if (WorkingDirectory != null && !Directory.Exists(WorkingDirectory)) + { + string message = StringUtil.Format(RemotingErrorIdStrings.StartJobWorkingDirectoryNotFound, WorkingDirectory); + var errorRecord = new ErrorRecord( + new DirectoryNotFoundException(message), + "DirectoryNotFoundException", + ErrorCategory.InvalidOperation, + targetObject: null); + + ThrowTerminatingError(errorRecord); + } + CommandDiscovery.AutoloadModulesWithJobSourceAdapters(this.Context, this.CommandOrigin); if (ParameterSetName == DefinitionNameParameterSet) @@ -628,6 +647,7 @@ protected override void CreateHelpersForSpecifiedComputerNames() connectionInfo.InitializationScript = _initScript; connectionInfo.AuthenticationMechanism = this.Authentication; connectionInfo.PSVersion = this.PSVersion; + connectionInfo.WorkingDirectory = this.WorkingDirectory; RemoteRunspace remoteRunspace = (RemoteRunspace)RunspaceFactory.CreateRunspace(connectionInfo, this.Host, Utils.GetTypeTableFromExecutionContextTLS()); diff --git a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs index 5e7a88c9495..2a3c04eecd9 100644 --- a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs +++ b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs @@ -1514,6 +1514,11 @@ internal sealed class NewProcessConnectionInfo : RunspaceConnectionInfo /// public bool RunAs32 { get; set; } + /// + /// Gets or sets an initial working directory for the powershell background process. + /// + public string WorkingDirectory { get; set; } + /// /// Powershell version to execute the job in. /// @@ -1590,6 +1595,7 @@ public NewProcessConnectionInfo Copy() NewProcessConnectionInfo result = new NewProcessConnectionInfo(_credential); result.AuthenticationMechanism = this.AuthenticationMechanism; result.InitializationScript = this.InitializationScript; + result.WorkingDirectory = this.WorkingDirectory; result.RunAs32 = this.RunAs32; result.PSVersion = this.PSVersion; result.Process = Process; diff --git a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs index 0ab8320853b..6c48e939f43 100644 --- a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs @@ -1912,7 +1912,7 @@ internal static PSEventArgs GetPSEventArgs(PSObject dataAsPSObject) string computerName = GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.PSEventArgsComputerName); Guid runspaceId = GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.PSEventArgsRunspaceId); - ArrayList sourceArgs = new ArrayList(); + var sourceArgs = new List(); foreach (object argument in RemotingDecoder.EnumerateListProperty(dataAsPSObject, RemoteDataNameStrings.PSEventArgsSourceArgs)) { sourceArgs.Add(argument); diff --git a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteDebuggingCapability.cs b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteDebuggingCapability.cs new file mode 100644 index 00000000000..c093e092636 --- /dev/null +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteDebuggingCapability.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.Management.Automation.Remoting +{ + /// + /// This class contains information about the debugging capability of the server side of the + /// MS-PSRDP connection. The functionality that is supported is determined by the PowerShell + /// version on the server. These capabilities will be used in remote debugging sessions to + /// determine what is supported by the server. + /// + internal class RemoteDebuggingCapability + { + private readonly HashSet _supportedCommands = new HashSet(); + + internal Version PSVersion { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The version of PowerShell used on the remote server debugger. + /// + /// + /// This should only be invoked by the static create method. + /// + private RemoteDebuggingCapability(Version powerShellVersion) + { + PSVersion = powerShellVersion; + + // Commands available in all server versions + _supportedCommands.Add(RemoteDebuggingCommands.GetDebuggerStopArgs); + _supportedCommands.Add(RemoteDebuggingCommands.SetDebuggerAction); + _supportedCommands.Add(RemoteDebuggingCommands.SetDebugMode); + + if (PSVersion == null) + { + return; + } + + // Commands added in v5 + if (PSVersion.Major >= PSVersionInfo.PSV5Version.Major) + { + _supportedCommands.Add(RemoteDebuggingCommands.SetDebuggerStepMode); + _supportedCommands.Add(RemoteDebuggingCommands.SetUnhandledBreakpointMode); + } + + // Commands added in v7 + if (PSVersion.Major >= PSVersionInfo.PSV7Version.Major) + { + _supportedCommands.Add(RemoteDebuggingCommands.GetBreakpoint); + _supportedCommands.Add(RemoteDebuggingCommands.SetBreakpoint); + _supportedCommands.Add(RemoteDebuggingCommands.EnableBreakpoint); + _supportedCommands.Add(RemoteDebuggingCommands.DisableBreakpoint); + _supportedCommands.Add(RemoteDebuggingCommands.RemoveBreakpoint); + } + } + + /// + /// Creates a instance that can be + /// used to identify the remoting capabilities of the server debugger. + /// + /// + /// The version of PowerShell used on the remote server debugger. + /// + /// + /// A new RemoteDebuggingCapability instance that is based on the version + /// of PowerShell used on the remote server debugger. + /// + internal static RemoteDebuggingCapability CreateDebuggingCapability(Version powerShellVersion) => + new RemoteDebuggingCapability(powerShellVersion); + + /// + /// Checks if a command is supported in the server version used to create + /// this instance. + /// + /// + /// The name of the command to check. + /// + /// + /// True if the command is supported; false otherwise. + /// + internal bool IsCommandSupported(string commandName) => + _supportedCommands.Contains(commandName); + } + + internal static class RemoteDebuggingCommands + { + #region DO NOT REMOVE OR CHANGE THE VALUES OF THESE CONSTANTS - it will break remote debugging compatibility with PowerShell + + // Commands related to debugger stop events + internal const string GetDebuggerStopArgs = "__Get-PSDebuggerStopArgs"; + internal const string SetDebuggerAction = "__Set-PSDebuggerAction"; + + // Miscellaneous debug commands + internal const string SetDebuggerStepMode = "__Set-PSDebuggerStepMode"; + internal const string SetDebugMode = "__Set-PSDebugMode"; + internal const string SetUnhandledBreakpointMode = "__Set-PSUnhandledBreakpointMode"; + + // Breakpoint commands + internal const string GetBreakpoint = "__Get-PSBreakpoint"; + internal const string SetBreakpoint = "__Set-PSBreakpoint"; + internal const string EnableBreakpoint = "__Enable-PSBreakpoint"; + internal const string DisableBreakpoint = "__Disable-PSBreakpoint"; + internal const string RemoveBreakpoint = "__Remove-PSBreakpoint"; + + #endregion + + internal static string CleanCommandName(string commandName) + { + return commandName.TrimStart('_'); + } + } +} diff --git a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs index cb20f3e2e18..3c7fd7445c2 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs @@ -1858,7 +1858,7 @@ private void MergeConfigHashIntoConfigHash(IDictionary childConfigHash) { string customizationString = customization.ToString(); - ArrayList customizationValue = new ArrayList(); + var customizationValue = new List(); // First, take all values from the master config table if (_configHash.ContainsKey(customizationString)) @@ -1866,7 +1866,7 @@ private void MergeConfigHashIntoConfigHash(IDictionary childConfigHash) IEnumerable existingValueAsCollection = LanguagePrimitives.GetEnumerable(_configHash[customization]); if (existingValueAsCollection != null) { - foreach (Object value in existingValueAsCollection) + foreach (object value in existingValueAsCollection) { customizationValue.Add(value); } @@ -1881,7 +1881,7 @@ private void MergeConfigHashIntoConfigHash(IDictionary childConfigHash) IEnumerable newValueAsCollection = LanguagePrimitives.GetEnumerable(childConfigHash[customization]); if (newValueAsCollection != null) { - foreach (Object value in newValueAsCollection) + foreach (object value in newValueAsCollection) { customizationValue.Add(value); } diff --git a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs index 7cb6143fc27..c23b875d5b3 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs @@ -1003,7 +1003,8 @@ internal override void CreateAsync() _processInstance = _connectionInfo.Process ?? new PowerShellProcessInstance(_connectionInfo.PSVersion, _connectionInfo.Credential, _connectionInfo.InitializationScript, - _connectionInfo.RunAs32); + _connectionInfo.RunAs32, + _connectionInfo.WorkingDirectory); if (_connectionInfo.Process != null) { _processCreated = false; diff --git a/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs b/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs index 5f6746eaeec..4d564ab4d21 100644 --- a/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs +++ b/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs @@ -87,7 +87,9 @@ protected void ProcessingThreadStart(object state) catch (Exception e) { PSEtwLog.LogOperationalError( - PSEventId.TransportError, PSOpcode.Open, PSTask.None, + PSEventId.TransportError, + PSOpcode.Open, + PSTask.None, PSKeyword.UseAlwaysOperational, Guid.Empty.ToString(), Guid.Empty.ToString(), @@ -96,7 +98,9 @@ protected void ProcessingThreadStart(object state) e.StackTrace); PSEtwLog.LogAnalyticError( - PSEventId.TransportError_Analytic, PSOpcode.Open, PSTask.None, + PSEventId.TransportError_Analytic, + PSOpcode.Open, + PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, Guid.Empty.ToString(), Guid.Empty.ToString(), @@ -158,7 +162,9 @@ protected void OnDataPacketReceived(byte[] rawData, string stream, Guid psGuid) protected void OnDataAckPacketReceived(Guid psGuid) { - throw new PSRemotingTransportException(PSRemotingErrorId.IPCUnknownElementReceived, RemotingErrorIdStrings.IPCUnknownElementReceived, + throw new PSRemotingTransportException( + PSRemotingErrorId.IPCUnknownElementReceived, + RemotingErrorIdStrings.IPCUnknownElementReceived, OutOfProcessUtils.PS_OUT_OF_PROC_DATA_ACK_TAG); } @@ -301,33 +307,39 @@ protected void OnCloseAckPacketReceived(Guid psGuid) #region Methods - protected OutOfProcessServerSessionTransportManager CreateSessionTransportManager(string configurationName, PSRemotingCryptoHelperServer cryptoHelper) + protected OutOfProcessServerSessionTransportManager CreateSessionTransportManager(string configurationName, PSRemotingCryptoHelperServer cryptoHelper, string workingDirectory) { PSSenderInfo senderInfo; #if !UNIX WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent(); - PSPrincipal userPrincipal = new PSPrincipal(new PSIdentity(string.Empty, true, currentIdentity.Name, null), + PSPrincipal userPrincipal = new PSPrincipal( + new PSIdentity(string.Empty, true, currentIdentity.Name, null), currentIdentity); senderInfo = new PSSenderInfo(userPrincipal, "http://localhost"); #else - PSPrincipal userPrincipal = new PSPrincipal(new PSIdentity(string.Empty, true, string.Empty, null), + PSPrincipal userPrincipal = new PSPrincipal( + new PSIdentity(string.Empty, true, string.Empty, null), null); senderInfo = new PSSenderInfo(userPrincipal, "http://localhost"); #endif OutOfProcessServerSessionTransportManager tm = new OutOfProcessServerSessionTransportManager(originalStdOut, originalStdErr, cryptoHelper); - ServerRemoteSession srvrRemoteSession = ServerRemoteSession.CreateServerRemoteSession(senderInfo, - _initialCommand, tm, configurationName); + ServerRemoteSession.CreateServerRemoteSession( + senderInfo, + _initialCommand, + tm, + configurationName, + workingDirectory); return tm; } - protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoHelper, string configurationName = null) + protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoHelper, string workingDirectory = null, string configurationName = null) { _initialCommand = initialCommand; - sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper); + sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper, workingDirectory); try { @@ -338,7 +350,7 @@ protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoH { if (sessionTM == null) { - sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper); + sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper, workingDirectory); } } @@ -352,8 +364,10 @@ protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoH sessionTM = null; } - throw new PSRemotingTransportException(PSRemotingErrorId.IPCUnknownElementReceived, - RemotingErrorIdStrings.IPCUnknownElementReceived, string.Empty); + throw new PSRemotingTransportException( + PSRemotingErrorId.IPCUnknownElementReceived, + RemotingErrorIdStrings.IPCUnknownElementReceived, + string.Empty); } // process data in a thread pool thread..this way Runspace, Command @@ -366,12 +380,15 @@ protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoH #else ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessingThreadStart), data); #endif - } while (true); + } + while (true); } catch (Exception e) { PSEtwLog.LogOperationalError( - PSEventId.TransportError, PSOpcode.Open, PSTask.None, + PSEventId.TransportError, + PSOpcode.Open, + PSTask.None, PSKeyword.UseAlwaysOperational, Guid.Empty.ToString(), Guid.Empty.ToString(), @@ -380,7 +397,9 @@ protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoH e.StackTrace); PSEtwLog.LogAnalyticError( - PSEventId.TransportError_Analytic, PSOpcode.Open, PSTask.None, + PSEventId.TransportError_Analytic, + PSOpcode.Open, + PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, Guid.Empty.ToString(), Guid.Empty.ToString(), @@ -468,8 +487,11 @@ private OutOfProcessMediator() : base(true) #region Static Methods /// + /// Starts the out-of-process powershell server instance. /// - internal static void Run(string initialCommand) + /// Specifies the initialization script. + /// Specifies the initial working directory. The working directory is set before the initial command. + internal static void Run(string initialCommand, string workingDirectory) { lock (SyncObject) { @@ -486,7 +508,7 @@ internal static void Run(string initialCommand) // Setup unhandled exception to log events AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomainUnhandledException); #endif - s_singletonInstance.Start(initialCommand, new PSRemotingCryptoHelperServer()); + s_singletonInstance.Start(initialCommand, new PSRemotingCryptoHelperServer(), workingDirectory); } #endregion diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs index 42005deac1e..de9a903f06f 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.IO; using System.Linq; using System.Management.Automation.Host; using System.Management.Automation.Internal; @@ -43,6 +44,9 @@ internal class ServerRunspacePoolDriver : IRSPDriverInvoke // local runspace pool at the server + // Optional initial location of the PowerShell session + private readonly string _initialLocation; + // Script to run after a RunspacePool/Runspace is created in this session. private ConfigurationDataFromXML _configData; @@ -96,18 +100,17 @@ private Dictionary _associatedShells #region Constructors /// - /// Creates the runspace pool driver. + /// Initializes a new instance of the runspace pool driver. /// /// Client runspace pool id to associate. - /// transport manager associated with this - /// runspace pool driver + /// Transport manager associated with this + /// runspace pool driver. /// Maximum runspaces to open. /// Minimum runspaces to open. /// Threading options for the runspaces in the pool. /// Apartment state for the runspaces in the pool. /// Host information about client side host. - /// - /// Contains: + /// Contains: /// 1. Script to run after a RunspacePool/Runspace is created in this session. /// For RunspacePool case, every newly created Runspace (in the pool) will run /// this script. @@ -120,6 +123,7 @@ private Dictionary _associatedShells /// Server capability reported to the client during negotiation (not the actual capability). /// Client PowerShell version. /// Optional endpoint configuration name to create a pushed configured runspace. + /// Optional initial location of the powershell. internal ServerRunspacePoolDriver( Guid clientRunspacePoolId, int minRunspaces, @@ -134,7 +138,8 @@ internal ServerRunspacePoolDriver( bool isAdministrator, RemoteSessionCapability serverCapability, Version psClientVersion, - string configurationName) + string configurationName, + string initialLocation) { Dbg.Assert(configData != null, "ConfigurationData cannot be null"); @@ -142,11 +147,12 @@ internal ServerRunspacePoolDriver( _clientPSVersion = psClientVersion; _configurationName = configurationName; + _initialLocation = initialLocation; // Create a new server host and associate for host call // integration - _remoteHost = new ServerDriverRemoteHost(clientRunspacePoolId, - Guid.Empty, hostInfo, transportManager, null); + _remoteHost = new ServerDriverRemoteHost( + clientRunspacePoolId, Guid.Empty, hostInfo, transportManager, null); _configData = configData; _applicationPrivateData = applicationPrivateData; @@ -615,6 +621,13 @@ private void HandleRunspaceCreated(object sender, RunspaceCreatedEventArgs args) // to ignore all but critical errors. } + if (!string.IsNullOrWhiteSpace(_initialLocation)) + { + var setLocationCommand = new Command("Set-Location"); + setLocationCommand.Parameters.Add(new CommandParameter("LiteralPath", _initialLocation)); + InvokeScript(setLocationCommand, args); + } + // Run startup scripts InvokeStartupScripts(args); @@ -704,9 +717,9 @@ private void HandleRunspacePoolForwardEvent(object sender, PSEventArgs e) /// /// Handle the invocation of powershell. /// - /// Sender of this event, unused. + /// Sender of this event, unused. /// Arguments describing this event. - private void HandleCreateAndInvokePowerShell(object sender, RemoteDataEventArgs> eventArgs) + private void HandleCreateAndInvokePowerShell(object _, RemoteDataEventArgs> eventArgs) { RemoteDataObject data = eventArgs.Data; @@ -1194,7 +1207,8 @@ private void StartPowerShellCommandOnPushedRunspace( private enum PreProcessCommandResult { /// - /// No debugger pre-processing. + /// No debugger pre-processing. "Get" commands use this so that the + /// data they retrieve can be sent back to the caller. /// None = 0, @@ -1204,11 +1218,6 @@ private enum PreProcessCommandResult /// ValidNotProcessed, - /// - /// GetDebuggerStopArgs. - /// - GetDebuggerStopArgs, - /// /// SetDebuggerAction. /// @@ -1227,7 +1236,7 @@ private enum PreProcessCommandResult /// /// SetPreserveUnhandledBreakpointMode. /// - SetPreserveUnhandledBreakpointMode + SetPreserveUnhandledBreakpointMode, }; private class DebuggerCommandArgument @@ -1266,35 +1275,31 @@ private static PreProcessCommandResult PreProcessDebuggerCommand( var command = commands.Commands[0]; string commandText = command.CommandText; - if (commandText.Equals(DebuggerUtils.GetDebuggerStopArgsFunctionName, StringComparison.OrdinalIgnoreCase)) + if (commandText.Equals(RemoteDebuggingCommands.GetDebuggerStopArgs, StringComparison.OrdinalIgnoreCase)) { - // // __Get-PSDebuggerStopArgs private virtual command. // No input parameters. // Returns DebuggerStopEventArgs object. - // // Evaluate this command only if the debugger is activated. - if (!isDebuggerActive) { return PreProcessCommandResult.ValidNotProcessed; } - - // Translate into debugger method call. - ScriptBlock scriptBlock = ScriptBlock.Create("$host.Runspace.Debugger.GetDebuggerStopArgs()"); - scriptBlock.LanguageMode = PSLanguageMode.FullLanguage; - commands.Clear(); - commands.AddCommand("Invoke-Command").AddParameter("ScriptBlock", scriptBlock).AddParameter("NoNewScope", true); + if (!isDebuggerActive) + { + return PreProcessCommandResult.ValidNotProcessed; + } - result = PreProcessCommandResult.GetDebuggerStopArgs; + ReplaceVirtualCommandWithScript(commands, "$host.Runspace.Debugger.GetDebuggerStopArgs()"); } - else if (commandText.Equals(DebuggerUtils.SetDebuggerActionFunctionName, StringComparison.OrdinalIgnoreCase)) + else if (commandText.Equals(RemoteDebuggingCommands.SetDebuggerAction, StringComparison.OrdinalIgnoreCase)) { - // // __Set-PSDebuggerAction private virtual command. // DebuggerResumeAction enum input parameter. // Returns void. - // // Evaluate this command only if the debugger is activated. - if (!isDebuggerActive) { return PreProcessCommandResult.ValidNotProcessed; } + if (!isDebuggerActive) + { + return PreProcessCommandResult.ValidNotProcessed; + } if ((command.Parameters == null) || (command.Parameters.Count == 0) || (!command.Parameters[0].Name.Equals("ResumeAction", StringComparison.OrdinalIgnoreCase))) @@ -1313,22 +1318,14 @@ private static PreProcessCommandResult PreProcessDebuggerCommand( catch (InvalidCastException) { } } - if (resumeAction == null) - { - throw new PSArgumentException("ResumeAction"); - } - - commandArgument.ResumeAction = resumeAction; + commandArgument.ResumeAction = resumeAction ?? throw new PSArgumentException("ResumeAction"); result = PreProcessCommandResult.SetDebuggerAction; } - else if (commandText.Equals(DebuggerUtils.SetDebugModeFunctionName, StringComparison.OrdinalIgnoreCase)) + else if (commandText.Equals(RemoteDebuggingCommands.SetDebugMode, StringComparison.OrdinalIgnoreCase)) { - // // __Set-PSDebugMode private virtual command. // DebugModes enum input parameter. // Returns void. - // - if ((command.Parameters == null) || (command.Parameters.Count == 0) || (!command.Parameters[0].Name.Equals("Mode", StringComparison.OrdinalIgnoreCase))) { @@ -1346,22 +1343,14 @@ private static PreProcessCommandResult PreProcessDebuggerCommand( catch (InvalidCastException) { } } - if (mode == null) - { - throw new PSArgumentException("Mode"); - } - - commandArgument.Mode = mode; + commandArgument.Mode = mode ?? throw new PSArgumentException("Mode"); result = PreProcessCommandResult.SetDebugMode; } - else if (commandText.Equals(DebuggerUtils.SetDebuggerStepMode, StringComparison.OrdinalIgnoreCase)) + else if (commandText.Equals(RemoteDebuggingCommands.SetDebuggerStepMode, StringComparison.OrdinalIgnoreCase)) { - // // __Set-PSDebuggerStepMode private virtual command. // Boolean Enabled input parameter. // Returns void. - // - if ((command.Parameters == null) || (command.Parameters.Count == 0) || (!command.Parameters[0].Name.Equals("Enabled", StringComparison.OrdinalIgnoreCase))) { @@ -1372,14 +1361,11 @@ private static PreProcessCommandResult PreProcessDebuggerCommand( commandArgument.DebuggerStepEnabled = enabled; result = PreProcessCommandResult.SetDebuggerStepMode; } - else if (commandText.Equals(DebuggerUtils.SetPSUnhandledBreakpointMode, StringComparison.OrdinalIgnoreCase)) + else if (commandText.Equals(RemoteDebuggingCommands.SetUnhandledBreakpointMode, StringComparison.OrdinalIgnoreCase)) { - // // __Set-PSUnhandledBreakpointMode private virtual command. // UnhandledBreakpointMode input parameter. // Returns void. - // - if ((command.Parameters == null) || (command.Parameters.Count == 0) || (!command.Parameters[0].Name.Equals("UnhandledBreakpointMode", StringComparison.OrdinalIgnoreCase))) { @@ -1397,18 +1383,168 @@ private static PreProcessCommandResult PreProcessDebuggerCommand( catch (InvalidCastException) { } } - if (mode == null) + commandArgument.UnhandledBreakpointMode = mode ?? throw new PSArgumentException("Mode"); + result = PreProcessCommandResult.SetPreserveUnhandledBreakpointMode; + } + else if (commandText.Equals(RemoteDebuggingCommands.GetBreakpoint, StringComparison.OrdinalIgnoreCase)) + { + // __Get-PSBreakpoint private virtual command. + // Input parameters: + // [-Id ] + // Returns Breakpoint object(s). + string script = null; + + if (command.Parameters?.Count > 0) { - throw new PSArgumentException("Mode"); + int breakpointId = CheckBreakpointIdParameter(command); + script = $"$host.Runspace.Debugger.GetBreakpoint({breakpointId})"; + } + else + { + script = $"$host.Runspace.Debugger.GetBreakpoints()"; } - commandArgument.UnhandledBreakpointMode = mode; - result = PreProcessCommandResult.SetPreserveUnhandledBreakpointMode; + ReplaceVirtualCommandWithScript(commands, script); + } + else if (commandText.Equals(RemoteDebuggingCommands.SetBreakpoint, StringComparison.OrdinalIgnoreCase)) + { + // __Set-PSBreakpoint private virtual command. + // Input parameters: + // [-Script] [-Line] [[-Column] ] [-Action ] + // [[-Script] ] -Command [-Action ] + // [[-Script] ] -Variable [-Action ] [-Mode ] + // Returns Breakpoint object. + if (command.Parameters == null || command.Parameters.Count == 0) + { + throw new PSArgumentException("You must provide at least one parameter."); + } + + string breakpointType = null; + foreach (var commandParameter in command.Parameters) + { + if (!commandParameter.Name.Equals("Script", StringComparison.OrdinalIgnoreCase) && + !commandParameter.Name.Equals("Line", StringComparison.OrdinalIgnoreCase) && + !commandParameter.Name.Equals("Column", StringComparison.OrdinalIgnoreCase) && + !commandParameter.Name.Equals("Action", StringComparison.OrdinalIgnoreCase) && + !commandParameter.Name.Equals("Command", StringComparison.OrdinalIgnoreCase) && + !commandParameter.Name.Equals("Variable", StringComparison.OrdinalIgnoreCase) && + !commandParameter.Name.Equals("Mode", StringComparison.OrdinalIgnoreCase)) + { + throw new PSArgumentException(commandParameter.Name); + } + + if (commandParameter.Name.Equals("Line", StringComparison.OrdinalIgnoreCase) || + commandParameter.Name.Equals("Column", StringComparison.OrdinalIgnoreCase)) + { + if (breakpointType != null && breakpointType != "Line") + { + throw new PSArgumentException(commandParameter.Name); + } + + breakpointType = "Line"; + } + else if (commandParameter.Name.Equals("Command", StringComparison.OrdinalIgnoreCase)) + { + if (breakpointType != null) + { + throw new PSArgumentException(commandParameter.Name); + } + + breakpointType = "Command"; + } + else if (commandParameter.Name.Equals("Variable", StringComparison.OrdinalIgnoreCase)) + { + if (breakpointType != null) + { + throw new PSArgumentException(commandParameter.Name); + } + + breakpointType = "Variable"; + } + } + + commands.Clear(); + commands.AddCommand("Set-PSBreakpoint"); + foreach (var commandParameter in command.Parameters) + { + var parameterValue = commandParameter.Name.Equals("Action", StringComparison.OrdinalIgnoreCase) + ? ScriptBlock.Create(commandParameter.Value as string) + : commandParameter.Value; + + commands.AddParameter(commandParameter.Name, parameterValue); + } + } + else if (commandText.Equals(RemoteDebuggingCommands.RemoveBreakpoint, StringComparison.OrdinalIgnoreCase)) + { + // __Remove-PSBreakpoint private virtual command. + // Input parameters: + // -Id + // Returns bool. + + int breakpointId = CheckBreakpointIdParameter(command); + + string script = $"$bp = $host.Runspace.Debugger.GetBreakpoint({breakpointId}); $bp -ne $null -and $host.Runspace.Debugger.RemoveBreakpoint($bp)"; + + ReplaceVirtualCommandWithScript(commands, script); + } + else if (commandText.Equals(RemoteDebuggingCommands.EnableBreakpoint, StringComparison.OrdinalIgnoreCase)) + { + // __Enable-PSBreakpoint private virtual command. + // Input parameters: + // -Id + // Returns Breakpoint. + + int breakpointId = CheckBreakpointIdParameter(command); + + string script = $"$bp = $host.Runspace.Debugger.GetBreakpoint({breakpointId}); if ($bp -ne $null) {{$host.Runspace.Debugger.EnableBreakpoint($bp)}}"; + + ReplaceVirtualCommandWithScript(commands, script); + } + else if (commandText.Equals(RemoteDebuggingCommands.DisableBreakpoint, StringComparison.OrdinalIgnoreCase)) + { + // __Disable-PSBreakpoint private virtual command. + // Input parameters: + // -Id + // Returns Breakpoint. + + int breakpointId = CheckBreakpointIdParameter(command); + + string script = $"$bp = $host.Runspace.Debugger.GetBreakpoint({breakpointId}); if ($bp -ne $null) {{$host.Runspace.Debugger.DisableBreakpoint($bp)}}"; + + ReplaceVirtualCommandWithScript(commands, script); } return result; } + private static void ReplaceVirtualCommandWithScript(PSCommand commands, string script) + { + ScriptBlock scriptBlock = ScriptBlock.Create(script); + scriptBlock.LanguageMode = PSLanguageMode.FullLanguage; + commands.Clear(); + commands.AddCommand("Invoke-Command") + .AddParameter("ScriptBlock", scriptBlock) + .AddParameter("NoNewScope", true); + } + + private static int CheckBreakpointIdParameter(Command command) + { + if (command.Parameters == null || + command.Parameters.Count == 0 || + !command.Parameters[0].Name.Equals("Id", StringComparison.OrdinalIgnoreCase)) + { + throw new PSArgumentException("Id"); + } + + int? breakpointId = command.Parameters[0].Value as int?; + if (breakpointId == null) + { + throw new PSArgumentException("Id"); + } + + return breakpointId.Value; + } + #endregion #region Private Classes @@ -1706,28 +1842,30 @@ public override bool InBreakpoint get { return _inDebugMode; } } - /// - /// Adds the provided set of breakpoints to the debugger. - /// - /// Breakpoints. - public override void SetBreakpoints(IEnumerable breakpoints) - { - _wrappedDebugger.Value.SetBreakpoints(breakpoints); - } - - /// - /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. - /// - /// Id of the breakpoint you want. public override Breakpoint GetBreakpoint(int id) => _wrappedDebugger.Value.GetBreakpoint(id); - /// - /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. - /// public override List GetBreakpoints() => _wrappedDebugger.Value.GetBreakpoints(); + public override CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action = null, string path = null) => + _wrappedDebugger.Value.SetCommandBreakpoint(command, action, path); + + public override LineBreakpoint SetLineBreakpoint(string path, int line, int column = 0, ScriptBlock action = null) => + _wrappedDebugger.Value.SetLineBreakpoint(path, line, column, action); + + public override VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode = VariableAccessMode.Write, ScriptBlock action = null, string path = null) => + _wrappedDebugger.Value.SetVariableBreakpoint(variableName, accessMode, action, path); + + public override bool RemoveBreakpoint(Breakpoint breakpoint) => + _wrappedDebugger.Value.RemoveBreakpoint(breakpoint); + + public override Breakpoint EnableBreakpoint(Breakpoint breakpoint) => + _wrappedDebugger.Value.EnableBreakpoint(breakpoint); + + public override Breakpoint DisableBreakpoint(Breakpoint breakpoint) => + _wrappedDebugger.Value.DisableBreakpoint(breakpoint); + /// /// Exits debugger mode with the provided resume action. /// @@ -1855,13 +1993,15 @@ internal override DebuggerCommand InternalProcessCommand(string command, IList

/// - /// Job object that is either a debuggable job or a container - /// of debuggable child jobs. + /// Job object that is either a debuggable job or a container of + /// debuggable child jobs. /// - internal override void DebugJob(Job job) - { - _wrappedDebugger.Value.DebugJob(job); - } + /// + /// If true, the debugger automatically invokes a break all when it + /// attaches to the job. + /// + internal override void DebugJob(Job job, bool breakAll) => + _wrappedDebugger.Value.DebugJob(job, breakAll); ///

/// Removes job from debugger job list and pops its @@ -1876,20 +2016,16 @@ internal override void StopDebugJob(Job job) /// /// Sets up debugger to debug provided Runspace in a nested debug session. /// - /// Runspace to debug. - internal override void DebugRunspace(Runspace runspace) - { - _wrappedDebugger.Value.DebugRunspace(runspace); - } - - /// - /// Sets up debugger to debug provided Runspace in a nested debug session. - /// - /// Runspace to debug. - /// - internal override void DebugRunspace(Runspace runspace, bool disableBreakAll) + /// + /// Runspace to debug. + /// + /// + /// When true, this command will invoke a BreakAll when the debugger is + /// first attached. + /// + internal override void DebugRunspace(Runspace runspace, bool breakAll) { - _wrappedDebugger.Value.DebugRunspace(runspace, disableBreakAll); + _wrappedDebugger.Value.DebugRunspace(runspace, breakAll); } /// diff --git a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs index 004f5fb00e9..dc740ec962a 100644 --- a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs +++ b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs @@ -89,6 +89,9 @@ internal class ServerRemoteSession : RemoteSession // Creates a pushed remote runspace session created with this configuration name. private string _configurationName; + // Specifies an initial location of the powershell session. + private string _initialLocation; + #region Events /// /// Raised when session is closed. @@ -176,6 +179,7 @@ internal ServerRemoteSession(PSSenderInfo senderInfo, /// /// /// Optional configuration endpoint name for OutOfProc sessions. + /// Optional configuration initial location of the powershell session. /// /// /// InitialSessionState provider with does @@ -188,13 +192,16 @@ internal ServerRemoteSession(PSSenderInfo senderInfo, ... */ - internal static ServerRemoteSession CreateServerRemoteSession(PSSenderInfo senderInfo, + internal static ServerRemoteSession CreateServerRemoteSession( + PSSenderInfo senderInfo, string configurationProviderId, string initializationParameters, AbstractServerSessionTransportManager transportManager, - string configurationName = null) + string configurationName = null, + string initialLocation = null) { - Dbg.Assert((senderInfo != null) & (senderInfo.UserInfo != null), + Dbg.Assert( + (senderInfo != null) && (senderInfo.UserInfo != null), "senderInfo and userInfo cannot be null."); s_trace.WriteLine("Finding InitialSessionState provider for id : {0}", configurationProviderId); @@ -207,12 +214,14 @@ internal static ServerRemoteSession CreateServerRemoteSession(PSSenderInfo sende string shellPrefix = System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix; int index = configurationProviderId.IndexOf(shellPrefix, StringComparison.OrdinalIgnoreCase); senderInfo.ConfigurationName = (index == 0) ? configurationProviderId.Substring(shellPrefix.Length) : string.Empty; - ServerRemoteSession result = new ServerRemoteSession(senderInfo, + ServerRemoteSession result = new ServerRemoteSession( + senderInfo, configurationProviderId, initializationParameters, transportManager) { - _configurationName = configurationName + _configurationName = configurationName, + _initialLocation = initialLocation }; // start state machine. @@ -229,14 +238,22 @@ internal static ServerRemoteSession CreateServerRemoteSession(PSSenderInfo sende /// /// /// + /// /// - internal static ServerRemoteSession CreateServerRemoteSession(PSSenderInfo senderInfo, + internal static ServerRemoteSession CreateServerRemoteSession( + PSSenderInfo senderInfo, string initializationScriptForOutOfProcessRunspace, AbstractServerSessionTransportManager transportManager, - string configurationName) + string configurationName, + string initialLocation) { - ServerRemoteSession result = CreateServerRemoteSession(senderInfo, - "Microsoft.PowerShell", string.Empty, transportManager, configurationName); + ServerRemoteSession result = CreateServerRemoteSession( + senderInfo, + "Microsoft.PowerShell", + string.Empty, + transportManager, + configurationName: configurationName, + initialLocation: initialLocation); result._initScriptForOutOfProcRS = initializationScriptForOutOfProcessRunspace; return result; } @@ -885,7 +902,8 @@ private void HandleCreateRunspacePool(object sender, RemoteDataEventArgs createR isAdministrator, Context.ServerCapability, psClientVersion, - _configurationName); + _configurationName, + _initialLocation); // attach the necessary event handlers and start the driver. Interlocked.Exchange(ref _runspacePoolDriver, tmpDriver); diff --git a/src/System.Management.Automation/help/HelpErrorTracer.cs b/src/System.Management.Automation/help/HelpErrorTracer.cs index 185601e52a9..6a2ae124955 100644 --- a/src/System.Management.Automation/help/HelpErrorTracer.cs +++ b/src/System.Management.Automation/help/HelpErrorTracer.cs @@ -1,9 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System.Collections; +using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Reflection; namespace System.Management.Automation { @@ -134,7 +133,7 @@ internal HelpErrorTracer(HelpSystem helpSystem) /// /// This tracks all live TraceFrame objects, which forms a stack. /// - private ArrayList _traceFrames = new ArrayList(); + private readonly List _traceFrames = new List(); /// /// This is the API to use for starting a help trace scope. @@ -160,7 +159,7 @@ internal void TraceError(ErrorRecord errorRecord) if (_traceFrames.Count <= 0) return; - TraceFrame traceFrame = (TraceFrame)_traceFrames[_traceFrames.Count - 1]; + TraceFrame traceFrame = _traceFrames[_traceFrames.Count - 1]; traceFrame.TraceError(errorRecord); } @@ -175,7 +174,7 @@ internal void TraceErrors(Collection errorRecords) if (_traceFrames.Count <= 0) return; - TraceFrame traceFrame = (TraceFrame)_traceFrames[_traceFrames.Count - 1]; + TraceFrame traceFrame = _traceFrames[_traceFrames.Count - 1]; traceFrame.TraceErrors(errorRecords); } @@ -185,7 +184,7 @@ internal void PopFrame(TraceFrame traceFrame) if (_traceFrames.Count <= 0) return; - TraceFrame lastFrame = (TraceFrame)_traceFrames[_traceFrames.Count - 1]; + TraceFrame lastFrame = _traceFrames[_traceFrames.Count - 1]; if (lastFrame == traceFrame) { diff --git a/src/System.Management.Automation/help/MUIFileSearcher.cs b/src/System.Management.Automation/help/MUIFileSearcher.cs index fb31de950c7..354cc9c4d10 100644 --- a/src/System.Management.Automation/help/MUIFileSearcher.cs +++ b/src/System.Management.Automation/help/MUIFileSearcher.cs @@ -118,7 +118,7 @@ private string[] GetFiles(string path, string pattern) #if UNIX // On Linux, file names are case sensitive, so we need to add // extra logic to select the files that match the given pattern. - ArrayList result = new ArrayList(); + var result = new List(); string[] files = Directory.GetFiles(path); var wildcardPattern = WildcardPattern.ContainsWildcardCharacters(pattern) @@ -143,7 +143,7 @@ private string[] GetFiles(string path, string pattern) } } - return (string[])result.ToArray(typeof(string)); + return result.ToArray(); #else return Directory.GetFiles(path, pattern); #endif diff --git a/src/System.Management.Automation/namespaces/FileSystemContentStream.cs b/src/System.Management.Automation/namespaces/FileSystemContentStream.cs index f18bb477028..4d7dae05950 100644 --- a/src/System.Management.Automation/namespaces/FileSystemContentStream.cs +++ b/src/System.Management.Automation/namespaces/FileSystemContentStream.cs @@ -344,7 +344,7 @@ public IList Read(long readCount) s_tracer.WriteLine("blocks requested = {0}", readCount); - ArrayList blocks = new ArrayList(); + var blocks = new List(); bool readToEnd = (readCount <= 0); if (_alreadyDetectEncoding && _reader.BaseStream.Position == 0) @@ -367,19 +367,19 @@ public IList Read(long readCount) if (_usingByteEncoding) { - if (!ReadByteEncoded(waitChanges, blocks, false)) + if (!ReadByteEncoded(waitChanges, blocks, readBackward: false)) break; } else { if (_usingDelimiter || _isRawStream) { - if (!ReadDelimited(waitChanges, blocks, false, _delimiter)) + if (!ReadDelimited(waitChanges, blocks, readBackward: false, _delimiter)) break; } else { - if (!ReadByLine(waitChanges, blocks, false)) + if (!ReadByLine(waitChanges, blocks, readBackward: false)) break; } } @@ -445,7 +445,7 @@ internal void SeekItemsBackward(int backCount) s_tracer.WriteLine("blocks seek backwards = {0}", backCount); - ArrayList blocks = new ArrayList(); + var blocks = new List(); if (_reader != null) { // Make the reader automatically detect the encoding @@ -463,13 +463,17 @@ internal void SeekItemsBackward(int backCount) return; } - StringBuilder builder = new StringBuilder(); - foreach (char character in _delimiter) - { - builder.Insert(0, character); - } + string actualDelimiter = string.Create( + _delimiter.Length, + _delimiter, + (chars, buf) => + { + for (int i = 0, j = buf.Length - 1; i < chars.Length; i++, j--) + { + chars[i] = buf[j]; + } + }); - string actualDelimiter = builder.ToString(); long currentBlock = 0; string lastDelimiterMatch = null; @@ -488,14 +492,14 @@ internal void SeekItemsBackward(int backCount) { if (_usingByteEncoding) { - if (!ReadByteEncoded(false, blocks, true)) + if (!ReadByteEncoded(waitChanges: false, blocks, readBackward: true)) break; } else { if (_usingDelimiter) { - if (!ReadDelimited(false, blocks, true, actualDelimiter)) + if (!ReadDelimited(waitChanges: false, blocks, readBackward: true, actualDelimiter)) break; // If the delimiter is at the end of the file, we need to read one more // to get to the right position. For example: @@ -508,7 +512,7 @@ internal void SeekItemsBackward(int backCount) } else { - if (!ReadByLine(false, blocks, true)) + if (!ReadByLine(waitChanges: false, blocks, readBackward: true)) break; } } @@ -553,7 +557,7 @@ internal void SeekItemsBackward(int backCount) } } - private bool ReadByLine(bool waitChanges, ArrayList blocks, bool readBackward) + private bool ReadByLine(bool waitChanges, List blocks, bool readBackward) { // Reading lines as strings string line = readBackward ? _backReader.ReadLine() : _reader.ReadLine(); @@ -567,21 +571,28 @@ private bool ReadByLine(bool waitChanges, ArrayList blocks, bool readBackward) { WaitForChanges(_path, _mode, _access, _share, _reader.CurrentEncoding); line = _reader.ReadLine(); - } while ((line == null) && (!_provider.Stopping)); + } + while ((line == null) && (!_provider.Stopping)); } } if (line != null) + { blocks.Add(line); + } int peekResult = readBackward ? _backReader.Peek() : _reader.Peek(); if (peekResult == -1) + { return false; + } else + { return true; + } } - private bool ReadDelimited(bool waitChanges, ArrayList blocks, bool readBackward, string actualDelimiter) + private bool ReadDelimited(bool waitChanges, List blocks, bool readBackward, string actualDelimiter) { if (_isRawStream) { @@ -631,7 +642,7 @@ private bool ReadDelimited(bool waitChanges, ArrayList blocks, bool readBackward // We only wait for changes when read forwards, so here we don't need to check if 'readBackward' is // true or false, we only use 'reader'. The member 'reader' will be updated by WaitForChanges. WaitForChanges(_path, _mode, _access, _share, _reader.CurrentEncoding); - numRead += _reader.Read(readBuffer.Slice(0, (currentOffset - numRead))); + numRead += _reader.Read(readBuffer.Slice(0, currentOffset - numRead)); } } } @@ -674,7 +685,8 @@ private bool ReadDelimited(bool waitChanges, ArrayList blocks, bool readBackward } } } - } while (delimiterNotFound && (numRead != 0)); + } + while (delimiterNotFound && (numRead != 0)); // We've reached the end of file or end of line. if (_currentLineContent.Length > 0) @@ -687,13 +699,14 @@ private bool ReadDelimited(bool waitChanges, ArrayList blocks, bool readBackward blocks.Add( !readBackward && !delimiterNotFound ? _currentLineContent.ToString(0, _currentLineContent.Length - actualDelimiter.Length) - : _currentLineContent.ToString() - ); + : _currentLineContent.ToString()); } int peekResult = readBackward ? _backReader.Peek() : _reader.Peek(); if (peekResult != -1) + { return true; + } else { if (readBackward && _currentLineContent.Length > 0) @@ -705,7 +718,7 @@ private bool ReadDelimited(bool waitChanges, ArrayList blocks, bool readBackward } } - private bool ReadByteEncoded(bool waitChanges, ArrayList blocks, bool readBack) + private bool ReadByteEncoded(bool waitChanges, List blocks, bool readBackward) { if (_isRawStream) { @@ -721,7 +734,9 @@ private bool ReadByteEncoded(bool waitChanges, ArrayList blocks, bool readBack) // Break when the end of the file is reached. if (n == 0) + { break; + } numBytesRead += n; numBytesToRead -= n; @@ -738,7 +753,7 @@ private bool ReadByteEncoded(bool waitChanges, ArrayList blocks, bool readBack) } } - if (readBack) + if (readBackward) { if (_stream.Position == 0) { @@ -773,7 +788,9 @@ private bool ReadByteEncoded(bool waitChanges, ArrayList blocks, bool readBack) return true; } else + { return false; + } } private void CreateStreams(string filePath, string streamName, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, Encoding fileEncoding) diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index c383f9493e9..88f2eb51ec4 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -1845,7 +1845,7 @@ private void Dir( // a) the user has asked to with the -FollowSymLinks switch parameter and // b) the directory pointed to by the symlink has not already been visited, // preventing symlink loops. - // c) it is not a reparse point with a target + // c) it is not a reparse point with a target (not OneDrive or an AppX link). if (tracker == null) { if (InternalSymbolicLinkLinkCodeMethods.IsReparsePointWithTarget(recursiveDirectory)) @@ -7712,6 +7712,7 @@ public static class InternalSymbolicLinkLinkCodeMethods // dwDesiredAccess of CreateFile internal enum FileDesiredAccess : uint { + GenericZero = 0, GenericRead = 0x80000000, GenericWrite = 0x40000000, GenericExecute = 0x20000000, @@ -7859,8 +7860,23 @@ internal static extern IntPtr CreateFile( FileAttributes dwFlagsAndAttributes, IntPtr hTemplateFile); - [DllImport(PinvokeDllNames.FindFirstFileDllName, EntryPoint = "FindFirstFileExW", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern SafeFileHandle FindFirstFileEx(string lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, ref WIN32_FIND_DATA lpFindFileData, FINDEX_SEARCH_OPS fSearchOp, IntPtr lpSearchFilter, int dwAdditionalFlags); + internal sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid + { + private SafeFindHandle() : base(true) { } + + protected override bool ReleaseHandle() + { + return FindClose(this.handle); + } + + [DllImport(PinvokeDllNames.FindCloseDllName)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool FindClose(IntPtr handle); + } + + // SetLastError is false as the use of this API doesn't not require GetLastError() to be called + [DllImport(PinvokeDllNames.FindFirstFileDllName, EntryPoint = "FindFirstFileExW", SetLastError = false, CharSet = CharSet.Unicode)] + private static extern SafeFindHandle FindFirstFileEx(string lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, ref WIN32_FIND_DATA lpFindFileData, FINDEX_SEARCH_OPS fSearchOp, IntPtr lpSearchFilter, int dwAdditionalFlags); internal enum FINDEX_INFO_LEVELS : uint { @@ -7902,7 +7918,11 @@ public static string GetTarget(PSObject instance) if (instance.BaseObject is FileSystemInfo fileSysInfo) { #if !UNIX - using (SafeFileHandle handle = OpenReparsePoint(fileSysInfo.FullName, FileDesiredAccess.GenericRead)) + // We set accessMode parameter to zero because documentation says: + // If this parameter is zero, the application can query certain metadata + // such as file, directory, or device attributes without accessing + // that file or device, even if GENERIC_READ access would have been denied. + using (SafeFileHandle handle = OpenReparsePoint(fileSysInfo.FullName, FileDesiredAccess.GenericZero)) { string linkTarget = WinInternalGetTarget(handle); @@ -7967,7 +7987,11 @@ private static string WinInternalGetLinkType(string filePath) throw new PlatformNotSupportedException(); } - using (SafeFileHandle handle = OpenReparsePoint(filePath, FileDesiredAccess.GenericRead)) + // We set accessMode parameter to zero because documentation says: + // If this parameter is zero, the application can query certain metadata + // such as file, directory, or device attributes without accessing + // that file or device, even if GENERIC_READ access would have been denied. + using (SafeFileHandle handle = OpenReparsePoint(filePath, FileDesiredAccess.GenericZero)) { int outBufferSize = Marshal.SizeOf(); @@ -8054,13 +8078,15 @@ internal static bool IsReparsePointWithTarget(FileSystemInfo fileInfo) return false; } #if !UNIX + // It is a reparse point and we should check some reparse point tags. var data = new WIN32_FIND_DATA(); - using (SafeFileHandle handle = FindFirstFileEx(fileInfo.FullName, FINDEX_INFO_LEVELS.FindExInfoBasic, ref data, FINDEX_SEARCH_OPS.FindExSearchNameMatch, IntPtr.Zero, 0)) + using (var handle = FindFirstFileEx(fileInfo.FullName, FINDEX_INFO_LEVELS.FindExInfoBasic, ref data, FINDEX_SEARCH_OPS.FindExSearchNameMatch, IntPtr.Zero, 0)) { - // Name surrogates (0x20000000) are reparse points that point to other named entities local to the filesystem (like symlinks) - // In the case of OneDrive, they are not surrogates and would be safe to recurse into. - // This code is equivalent to the IsReparseTagNameSurrogate macro: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-isreparsetagnamesurrogate - if (!handle.IsInvalid && (data.dwReserved0 & 0x20000000) == 0 && (data.dwReserved0 & IO_REPARSE_TAG_APPEXECLINK) != IO_REPARSE_TAG_APPEXECLINK) + // The name surrogate bit 0x20000000 is defined in https://docs.microsoft.com/en-us/windows/win32/fileio/reparse-point-tags + // Name surrogates (0x20000000) are reparse points that point to other named entities local to the filesystem + // (like symlinks and mount points). + // In the case of OneDrive, they are not name surrogates and would be safe to recurse into. + if (!handle.IsInvalid && (data.dwReserved0 & 0x20000000) == 0 && (data.dwReserved0 != IO_REPARSE_TAG_APPEXECLINK)) { return false; } diff --git a/src/System.Management.Automation/resources/DebuggerStrings.resx b/src/System.Management.Automation/resources/DebuggerStrings.resx index 90cb124142f..79b0cf42a46 100644 --- a/src/System.Management.Automation/resources/DebuggerStrings.resx +++ b/src/System.Management.Automation/resources/DebuggerStrings.resx @@ -237,6 +237,9 @@ The current session does not support debugging; operation will continue. Cannot push a debugger object onto itself. + + The {0} command is not supported for remote use in the version of PowerShell that is running in the remote runspace. + Process diff --git a/src/System.Management.Automation/resources/ErrorPackage.resx b/src/System.Management.Automation/resources/ErrorPackage.resx index 49d1e8c2fc7..04c0467ba71 100644 --- a/src/System.Management.Automation/resources/ErrorPackage.resx +++ b/src/System.Management.Automation/resources/ErrorPackage.resx @@ -126,18 +126,13 @@ Object "{0}" is reported as an error. - - The action preference of "Suspend" is supported only for ErrorAction. - "Suspend" and ErrorAction should not be localized - - - The error action preference of "Suspend" is supported only on workflows. - "Suspend" should not be localized - it is a literal. - The value {0} is not supported for an ActionPreference variable. The provided value should be used only as a value for a preference parameter, and has been replaced by the default value. For more information, see the Help topic, "about_Preference_Variables." - - The value {0} is not supported for an ActionPreference variable. The provided value should be used only as a value for a preference parameter. For more information, see the Help topic, "about_Preference_Variables." + + The {0} ActionPreference value is reserved for future use and is not supported at this time. For more information about preference variables, see the Help topic, "about_Preference_Variables." + + + The {0} ActionPreference value is reserved for future use and is not supported at this time. It has been replaced in your {1} variable by the default value of {2}. For more information about preference variables, see the Help topic, "about_Preference_Variables." diff --git a/src/System.Management.Automation/resources/FileSystemProviderStrings.resx b/src/System.Management.Automation/resources/FileSystemProviderStrings.resx index 2d2a36aa27f..1b4d1159e59 100644 --- a/src/System.Management.Automation/resources/FileSystemProviderStrings.resx +++ b/src/System.Management.Automation/resources/FileSystemProviderStrings.resx @@ -253,7 +253,7 @@ Cannot process the file because the file {0} was not found. - Directory: + Directory: Cannot detect the encoding of the file. The specified encoding {0} is not supported when the content is read in reverse. diff --git a/src/System.Management.Automation/resources/InternalCommandStrings.resx b/src/System.Management.Automation/resources/InternalCommandStrings.resx index 153bb3e0bbb..4f2705670bc 100644 --- a/src/System.Management.Automation/resources/InternalCommandStrings.resx +++ b/src/System.Management.Automation/resources/InternalCommandStrings.resx @@ -181,4 +181,7 @@ The following common parameters are not currently supported in the Parallel parameter set: ErrorAction, WarningAction, InformationAction, PipelineVariable + + An unexpected error has occurred while processing ForEach-Object -Parallel input. This may mean that some of the piped input did not get processed. Error: {0}. + \ No newline at end of file diff --git a/src/System.Management.Automation/resources/ParserStrings.resx b/src/System.Management.Automation/resources/ParserStrings.resx index 4644fa5aa3b..7d68e49d66b 100644 --- a/src/System.Management.Automation/resources/ParserStrings.resx +++ b/src/System.Management.Automation/resources/ParserStrings.resx @@ -1500,4 +1500,7 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent Class keyword is not allowed in ConstrainedLanguage mode. + + Missing ':' in the ternary expression. + diff --git a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx index d1bb9290151..db4c55e986e 100644 --- a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx +++ b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx @@ -1301,6 +1301,9 @@ All WinRM sessions connected to PowerShell session configurations, such as Micro Cannot find a scheduled job with type {0} and name {1}. {0} is the job definition type and {1} is the job definition name. + + Cannot find the WorkingDirectory path {0}. + Cannot connect to session {0}. The session no longer exists on computer {1}. {0} is the session name that cannot be found. diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index 434532b4430..77c3f21242f 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -1714,6 +1714,9 @@ internal static void WinUninitialize() { CloseSession(); + // Unregister the event handler. + AppDomain.CurrentDomain.ProcessExit -= CurrentDomain_ProcessExit; + // Uninitialize the AMSI interface. AmsiCleanedUp = true; AmsiNativeMethods.AmsiUninitialize(s_amsiContext); diff --git a/src/System.Management.Automation/utils/FuzzyMatch.cs b/src/System.Management.Automation/utils/FuzzyMatch.cs index 0c99370c109..a99f9a5bf5c 100644 --- a/src/System.Management.Automation/utils/FuzzyMatch.cs +++ b/src/System.Management.Automation/utils/FuzzyMatch.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; +using System.Globalization; namespace System.Management.Automation { @@ -23,7 +23,7 @@ public static bool IsFuzzyMatch(string string1, string string2) /// - /// Compute the distance between two strings. + /// Compute the case-insensitive distance between two strings. /// Based off https://www.csharpstar.com/csharp-string-distance-algorithm/. /// /// The first string to compare. @@ -31,6 +31,9 @@ public static bool IsFuzzyMatch(string string1, string string2) /// The distance value where the lower the value the shorter the distance between the two strings representing a closer match. public static int GetDamerauLevenshteinDistance(string string1, string string2) { + string1 = string1.ToUpper(CultureInfo.CurrentCulture); + string2 = string2.ToUpper(CultureInfo.CurrentCulture); + var bounds = new { Height = string1.Length + 1, Width = string2.Length + 1 }; int[,] matrix = new int[bounds.Height, bounds.Width]; diff --git a/src/System.Management.Automation/utils/Telemetry.cs b/src/System.Management.Automation/utils/Telemetry.cs index c7d40c1377e..aceb8fd5e28 100644 --- a/src/System.Management.Automation/utils/Telemetry.cs +++ b/src/System.Management.Automation/utils/Telemetry.cs @@ -104,12 +104,14 @@ static ApplicationInsightsTelemetry() { s_telemetryClient = new TelemetryClient(); TelemetryConfiguration.Active.InstrumentationKey = _psCoreTelemetryKey; + // Set this to true to reduce latency during development TelemetryConfiguration.Active.TelemetryChannel.DeveloperMode = false; s_sessionId = Guid.NewGuid().ToString(); // use a hashset when looking for module names, it should be quicker than a string comparison - s_knownModules = new HashSet(StringComparer.OrdinalIgnoreCase) { + s_knownModules = new HashSet(StringComparer.OrdinalIgnoreCase) + { "Microsoft.PowerShell.Archive", "Microsoft.PowerShell.Host", "Microsoft.PowerShell.Management", @@ -122,6 +124,7 @@ static ApplicationInsightsTelemetry() "PSReadLine", "ThreadJob", }; + s_uniqueUserIdentifier = GetUniqueIdentifier().ToString(); } } @@ -193,7 +196,6 @@ internal static void SendTelemetryMetric(TelemetryType metricId, string data) // do nothing, telemetry can't be sent // don't send the panic telemetry as if we have failed above, it will likely fail here. } - } // Get the experimental feature name. If we can report it, we'll return the name of the feature, otherwise, we'll return "anonymous" @@ -233,7 +235,9 @@ internal static void SendPSCoreStartupTelemetry(string mode) { return; } + var properties = new Dictionary(); + // The variable POWERSHELL_DISTRIBUTION_CHANNEL is set in our docker images. // This allows us to track the actual docker OS as OSDescription provides only "linuxkit" // which has limited usefulness @@ -354,7 +358,7 @@ private static bool TryCreateUniqueIdentifierAndFile(string telemetryFilePath, o /// A guid which represents the unique identifier. private static Guid GetUniqueIdentifier() { - // Try to get the unique id. If this returns false, we'll + // Try to get the unique id. If this returns false, we'll // create/recreate the telemetry.uuid file to persist for next startup. Guid id = Guid.Empty; string uuidPath = Path.Join(Platform.CacheDirectory, "telemetry.uuid"); @@ -363,7 +367,7 @@ private static Guid GetUniqueIdentifier() return id; } - // Multiple processes may start simultaneously so we need a system wide + // Multiple processes may start simultaneously so we need a system wide // way to control access to the file in the case (although remote) when we have // simulataneous shell starts without the persisted file which attempt to create the file. using (var m = new Mutex(true, "CreateUniqueUserId")) diff --git a/src/powershell-native/Install-PowerShellRemoting.ps1 b/src/powershell-native/Install-PowerShellRemoting.ps1 index 19a80fdc8a1..afdac53df25 100644 --- a/src/powershell-native/Install-PowerShellRemoting.ps1 +++ b/src/powershell-native/Install-PowerShellRemoting.ps1 @@ -123,8 +123,8 @@ function Install-PluginEndpoint { # Install the plugin # # # ###################### - - if ($PowerShellHome -ne $null) + + if (-not [String]::IsNullOrEmpty($PowerShellHome)) { $targetPsHome = $PowerShellHome $targetPsVersion = & "$targetPsHome\pwsh" -NoProfile -Command '$PSVersionTable.PSVersion.ToString()' diff --git a/src/powershell-unix/powershell-unix.csproj b/src/powershell-unix/powershell-unix.csproj index e24c81471f6..5e5e9bb0c6c 100644 --- a/src/powershell-unix/powershell-unix.csproj +++ b/src/powershell-unix/powershell-unix.csproj @@ -38,7 +38,6 @@ - diff --git a/src/powershell-win-core/powershell-win-core.csproj b/src/powershell-win-core/powershell-win-core.csproj index f1ca7c18ac2..ac1486d91fc 100644 --- a/src/powershell-win-core/powershell-win-core.csproj +++ b/src/powershell-win-core/powershell-win-core.csproj @@ -35,7 +35,7 @@ PreserveNewest PreserveNewest - + PreserveNewest PreserveNewest @@ -53,7 +53,6 @@ - diff --git a/test/hosting/test_HostingBasic.cs b/test/hosting/test_HostingBasic.cs index 164f1f02aae..b16cd5ba0ec 100644 --- a/test/hosting/test_HostingBasic.cs +++ b/test/hosting/test_HostingBasic.cs @@ -139,5 +139,40 @@ public static void TestCommandFromNative() File.Delete(target); } } + + /// + /// Reference assemblies should be handled correctly so that Add-Type works in the hosting scenario. + /// + [Fact] + public static void TestAddTypeCmdletInHostScenario() + { + string code = @" + using System; + public class Foo + { + public Foo(string name, string path) + { + this.Name = name; + this.Path = path; + } + + public string Name; + public string Path; + } + "; + + using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) + { + ps.AddCommand("Add-Type").AddParameter("TypeDefinition", code).Invoke(); + ps.Commands.Clear(); + + var results = ps.AddScript("[Foo]::new('Joe', 'Unknown')").Invoke(); + Assert.Single(results); + + dynamic foo = results[0]; + Assert.Equal("Joe", foo.Name); + Assert.Equal("Unknown", foo.Path); + } + } } } diff --git a/test/powershell/Host/ConsoleHost.Tests.ps1 b/test/powershell/Host/ConsoleHost.Tests.ps1 index 12ced856831..e0054f7be6d 100644 --- a/test/powershell/Host/ConsoleHost.Tests.ps1 +++ b/test/powershell/Host/ConsoleHost.Tests.ps1 @@ -91,6 +91,10 @@ Describe "ConsoleHost unit tests" -tags "Feature" { $Error.Clear() } + It "Clear-Host does not injects data into PowerShell output stream" { + & { Clear-Host; 'hi' } | Should -BeExactly 'hi' + } + Context "ShellInterop" { It "Verify Parsing Error Output Format Single Shell should throw exception" { { & $powershell -outp blah -comm { $input } } | Should -Throw -ErrorId "IncorrectValueForFormatParameter" diff --git a/test/powershell/Host/Startup.Tests.ps1 b/test/powershell/Host/Startup.Tests.ps1 index 41c1749c575..6fb2b7336ac 100644 --- a/test/powershell/Host/Startup.Tests.ps1 +++ b/test/powershell/Host/Startup.Tests.ps1 @@ -103,6 +103,11 @@ Describe "Validate start of console host" -Tag CI { } It "No new assemblies are loaded" { + if ( (Get-PlatformInfo) -eq "alpine" ) { + Set-ItResult -Pending -Because "Missing MI library causes list to be different" + return + } + $diffs = Compare-Object -ReferenceObject $allowedAssemblies -DifferenceObject $loadedAssemblies if ($null -ne $diffs) { diff --git a/test/powershell/Language/Operators/TernaryOperator.Tests.ps1 b/test/powershell/Language/Operators/TernaryOperator.Tests.ps1 new file mode 100644 index 00000000000..ce48c98c6f5 --- /dev/null +++ b/test/powershell/Language/Operators/TernaryOperator.Tests.ps1 @@ -0,0 +1,111 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +Describe "Using of ternary operator" -Tags CI { + BeforeAll { + $skipTest = -not $EnabledExperimentalFeatures.Contains('PSTernaryOperator') + if ($skipTest) { + Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSTernaryOperator' to be enabled." -Verbose + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = $true + } + else { + $testCases = @( + ## Condition: variable and constant expressions + @{ Script = { $true ? 1 : 2 }; ExpectedValue = 1 } + @{ Script = { $true? ?1 :2 }; ExpectedValue = 2 } + @{ Script = { ${true}?1:2 }; ExpectedValue = 1 } + @{ Script = { 1 ? 1kb : 0xf }; ExpectedValue = 1kb } + @{ Script = { 0 ?1kb:0xf }; ExpectedValue = 15 } + @{ Script = { 's' ?1kb:0xf }; ExpectedValue = 1kb } + @{ Script = { $null ?1kb:0xf }; ExpectedValue = 15 } + @{ Script = { '' ?1kb:0xf }; ExpectedValue = 15 } + + ## Condition: other primary expressions + @{ Script = { 1,2,3,4 ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { @(1,2,3,4) ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { @{name = 'name'} ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { @{name = 'name'}.name ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { @{name = 'name'}.Contains('name') ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { (Test-Path Env:\NonExist) ? 'true' : 'false' }; ExpectedValue = 'false' } + @{ Script = { (Test-Path Env:\PSModulePath) ? 'true' : 'false' }; ExpectedValue = 'true' } + @{ Script = { $($p = Get-Process -Id $PID; $p.Name -eq 'pwsh') ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { ($a = 1) ? 2 : 3 }; ExpectedValue = 2 } + @{ Script = { $($a = 1) ? 2 : 3 }; ExpectedValue = 3 } + @{ Script = { (Write-Warning -Message warning -WarningAction SilentlyContinue) ? 1 : 2 }; ExpectedValue = 2 } + @{ Script = { (Write-Error -Message error -ErrorAction SilentlyContinue) ? 1 : 2 }; ExpectedValue = 2 } + + ## Condition: unary and binary expression expressions + @{ Script = { -not $IsCoreCLR ? 'Desktop' : 'Core' }; ExpectedValue = 'Core' } + @{ Script = { $PSEdition -eq 'Core' ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { $IsCoreCLR -and (Get-Process -Id $PID).Name -eq 'pwsh' ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { $IsCoreCLR -and 'pwsh' -match 'p.*h' ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { 1,2,3 -contains 2 ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + + ## Nested ternary expressions + @{ Script = { $IsCoreCLR ? $false ? 'nested-if-true' : 'nested-if-false' : 'if-false' }; ExpectedValue = 'nested-if-false' } + @{ Script = { $IsCoreCLR ? $false ? 'nested-if-true' : $true ? 'nested-nested-if-true' : 'nested-nested-if-false' : 'if-false' }; ExpectedValue = 'nested-nested-if-true' } + + ## Binary operator has higher precedence order than ternary + @{ Script = { !$IsCoreCLR ? 'Core' : 'Desktop' -eq 'Core' }; ExpectedValue = !$IsCoreCLR ? 'Core' : ('Desktop' -eq 'Core') } + @{ Script = { ($IsCoreCLR ? 'Core' : 'Desktop') -eq 'Core' }; ExpectedValue = $true } + ) + } + } + + AfterAll { + if ($skipTest) { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + } + } + + It "Ternary expression '