diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 19974c8891b..043cf0432f7 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; @@ -16,134 +17,126 @@ 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 })] - public class TestConnectionCommand : PSCmdlet + [Cmdlet(VerbsDiagnostic.Test, "Connection", DefaultParameterSetName = PingSet, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135266")] + [OutputType(typeof(PingStatus), ParameterSetName = new[] { PingSet, RepeatPingSet })] + [OutputType(typeof(TraceStatus), ParameterSetName = new[] { TraceRouteSet })] + [OutputType(typeof(PingMtuStatus), ParameterSetName = new[] { MtuSizeDetectSet })] + [OutputType(typeof(bool), ParameterSetName = new[] { PingSet, RepeatPingSet, TcpPortSet })] + [OutputType(typeof(int), ParameterSetName = new[] { MtuSizeDetectSet })] + public class TestConnectionCommand : PSCmdlet, IDisposable { - 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 PingSet = "Ping"; + private const string RepeatPingSet = "RepeatPing"; + private const string TraceRouteSet = "TraceRoute"; + private const string TcpPortSet = "TcpPort"; + private const string MtuSizeDetectSet = "MtuSizeDetect"; + private const int DefaultMaxHops = 128; + + private readonly Ping _sender = new Ping(); #region Parameters /// - /// Do ping test. + /// Gets or sets the Ping parameter. /// - [Parameter(ParameterSetName = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] + [Parameter(ParameterSetName = PingSet)] + [Parameter(ParameterSetName = RepeatPingSet)] public SwitchParameter Ping { get; set; } = true; /// - /// Force using IPv4 protocol. + /// Gets or sets whether to force use of the IPv4 protocol. /// - [Parameter(ParameterSetName = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] - [Parameter(ParameterSetName = ParameterSetTraceRoute)] - [Parameter(ParameterSetName = ParameterSetDetectionOfMTUSize)] - [Parameter(ParameterSetName = ParameterSetConnectionByTCPPort)] + [Parameter] public SwitchParameter IPv4 { get; set; } /// - /// Force using IPv6 protocol. + /// Gets or sets whether to force use of the IPv6 protocol. /// - [Parameter(ParameterSetName = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] - [Parameter(ParameterSetName = ParameterSetTraceRoute)] - [Parameter(ParameterSetName = ParameterSetDetectionOfMTUSize)] - [Parameter(ParameterSetName = ParameterSetConnectionByTCPPort)] + [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(ParameterSetName = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] - [Parameter(ParameterSetName = ParameterSetTraceRoute)] - [Parameter(ParameterSetName = ParameterSetDetectionOfMTUSize)] - [Parameter(ParameterSetName = ParameterSetConnectionByTCPPort)] + [Parameter] public SwitchParameter ResolveDestination { get; set; } /// - /// Source from which to do a test (ping, trace route, ...). - /// The default is Local Host. + /// Gets or sets the source hostname from which to perform a test (ping, trace route, ...). + /// The default is localhost. /// Remoting is not yet implemented internally in the cmdlet. /// - [Parameter(ParameterSetName = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] - [Parameter(ParameterSetName = ParameterSetTraceRoute)] - [Parameter(ParameterSetName = ParameterSetConnectionByTCPPort)] - public string Source { get; } = Dns.GetHostName(); + [Parameter(ParameterSetName = PingSet)] + [Parameter(ParameterSetName = RepeatPingSet)] + [Parameter(ParameterSetName = TraceRouteSet)] + [Parameter(ParameterSetName = TcpPortSet)] + public string Source { get; set; } = 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 = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] - [Parameter(ParameterSetName = ParameterSetTraceRoute)] - [ValidateRange(0, sMaxHops)] + [Parameter(ParameterSetName = PingSet)] + [Parameter(ParameterSetName = RepeatPingSet)] + [Parameter(ParameterSetName = TraceRouteSet)] + [ValidateRange(0, DefaultMaxHops)] [Alias("Ttl", "TimeToLive", "Hops")] - public int MaxHops { get; set; } = sMaxHops; - - private const int sMaxHops = 128; + public int MaxHops { get; set; } = DefaultMaxHops; /// - /// Count of attempts. + /// Gets or sets the number of pings to attempt. /// The default (from Windows) is 4 times. /// - [Parameter(ParameterSetName = ParameterSetPingCount)] + [Parameter(ParameterSetName = PingSet)] [ValidateRange(ValidateRangeKind.Positive)] 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 = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] + [Parameter(ParameterSetName = PingSet)] + [Parameter(ParameterSetName = RepeatPingSet)] [ValidateRange(ValidateRangeKind.Positive)] public int Delay { get; set; } = 1; /// - /// Buffer size to send. + /// Gets or sets the size of the buffer to send. /// The default (from Windows) is 32 bites. /// Max value is 65500 (limit from Windows API). /// - [Parameter(ParameterSetName = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] + [Parameter(ParameterSetName = PingSet)] + [Parameter(ParameterSetName = RepeatPingSet)] [Alias("Size", "Bytes", "BS")] [ValidateRange(0, 65500)] public int BufferSize { get; set; } = DefaultSendBufferSize; /// - /// Don't fragment ICMP packages. + /// Gets or sets whether to prevent fragmentation of ICMP packages. /// Currently CoreFX not supports this on Unix. /// - [Parameter(ParameterSetName = ParameterSetPingCount)] - [Parameter(ParameterSetName = ParameterSetPingContinues)] + [Parameter(ParameterSetName = PingSet)] + [Parameter(ParameterSetName = RepeatPingSet)] public SwitchParameter DontFragment { get; set; } /// - /// Continue ping until user press Ctrl-C - /// or Int.MaxValue threshold reached. + /// Gets or sets whether to ping the target endlessly, until the user presses Ctrl+C or the maximum threshold of + /// Int.MaxValue pings is reached. /// - [Parameter(ParameterSetName = ParameterSetPingContinues)] - public SwitchParameter Continues { get; set; } + [Parameter(Mandatory = true, ParameterSetName = RepeatPingSet)] + [Alias("Continues")] + public SwitchParameter Repeat { get; set; } /// - /// Set short output kind ('bool' for Ping, 'int' for MTU size ...). + /// Gets or sets whether to return simplified output. /// Default is to return typed result object(s). + /// When used with standard -Ping parameter set or -TraceRoute, a simple $true/$false value is returned. /// - [Parameter()] - public SwitchParameter Quiet; + [Parameter] + public SwitchParameter Quiet { get; set; } /// /// Time-out value in seconds. @@ -151,39 +144,41 @@ 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; /// /// 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; } /// - /// Detect MTU size. + /// Gets or sets whether to detect MTU size. /// - [Parameter(Mandatory = true, ParameterSetName = ParameterSetDetectionOfMTUSize)] - public SwitchParameter MTUSizeDetect { get; set; } + [Parameter(Mandatory = true, ParameterSetName = MtuSizeDetectSet)] + [Alias("MtuSizeDetect")] + public SwitchParameter DetectMtuSize { get; set; } /// - /// Do traceroute test. + /// Gets or sets whether to perform a traceroute test. /// - [Parameter(Mandatory = true, ParameterSetName = ParameterSetTraceRoute)] + [Parameter(Mandatory = true, ParameterSetName = TraceRouteSet)] public SwitchParameter Traceroute { get; set; } /// - /// Do tcp connection test. + /// Gets or sets whether to do a tcp connection test on the specified port. /// [ValidateRange(0, 65535)] - [Parameter(Mandatory = true, ParameterSetName = ParameterSetConnectionByTCPPort)] - public int TCPPort { get; set; } + [Parameter(Mandatory = true, ParameterSetName = TcpPortSet)] + public int TcpPort { get; set; } #endregion Parameters @@ -192,13 +187,9 @@ public class TestConnectionCommand : PSCmdlet /// protected override void BeginProcessing() { - base.BeginProcessing(); - - switch (ParameterSetName) + if (ParameterSetName == RepeatPingSet) { - case ParameterSetPingContinues: - Count = int.MaxValue; - break; + Count = int.MaxValue; } } @@ -211,17 +202,17 @@ protected override void ProcessRecord() { switch (ParameterSetName) { - case ParameterSetPingCount: - case ParameterSetPingContinues: + case PingSet: + 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; } @@ -230,29 +221,21 @@ protected override void ProcessRecord() #region ConnectionTest - private void ProcessConnectionByTCPPort(String targetNameOrAddress) + private void ProcessConnectionByTCPPort(string targetNameOrAddress) { - string resolvedTargetName = null; - IPAddress targetAddress = null; - - if (!InitProcessPing(targetNameOrAddress, out resolvedTargetName, out targetAddress)) + if (!InitProcessPing(targetNameOrAddress, out _, out IPAddress targetAddress)) { return; } - WriteConnectionTestHeader(resolvedTargetName, targetAddress.ToString()); - - TcpClient client = new TcpClient(); + var client = new TcpClient(); try { - Task connectionTask = client.ConnectAsync(targetAddress, TCPPort); - string targetString = targetAddress.ToString(); + Task connectionTask = client.ConnectAsync(targetAddress, TcpPort); for (var i = 1; i <= TimeoutSeconds; i++) { - WriteConnectionTestProgress(targetNameOrAddress, targetString, i); - Task timeoutTask = Task.Delay(millisecondsDelay: 1000); Task.WhenAny(connectionTask, timeoutTask).Result.Wait(); @@ -277,273 +260,154 @@ private void ProcessConnectionByTCPPort(String targetNameOrAddress) finally { client.Close(); - WriteConnectionTestFooter(); } WriteObject(false); } - private void WriteConnectionTestHeader(string resolvedTargetName, string targetAddress) - { - _testConnectionProgressBarActivity = StringUtil.Format(TestConnectionResources.ConnectionTestStart, resolvedTargetName, targetAddress); - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace); - WriteProgress(record); - } - - private void WriteConnectionTestProgress(string targetNameOrAddress, string targetAddress, int timeout) - { - var msg = StringUtil.Format(TestConnectionResources.ConnectionTestDescription, - targetNameOrAddress, - targetAddress, - timeout); - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, msg); - WriteProgress(record); - } - - private void WriteConnectionTestFooter() - { - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace); - record.RecordType = ProgressRecordType.Completed; - WriteProgress(record); - } - #endregion ConnectionTest #region TracerouteTest - private void ProcessTraceroute(String targetNameOrAddress) + private void ProcessTraceroute(string targetNameOrAddress) { - string resolvedTargetName = null; - IPAddress targetAddress = null; byte[] buffer = GetSendBuffer(BufferSize); - if (!InitProcessPing(targetNameOrAddress, out resolvedTargetName, out targetAddress)) + if (!InitProcessPing(targetNameOrAddress, out string resolvedTargetName, out IPAddress targetAddress)) { return; } - WriteConsoleTraceRouteHeader(resolvedTargetName, targetAddress.ToString()); + WriteVerbose(StringUtil.Format( + TestConnectionResources.TraceRouteStart, + resolvedTargetName, + targetAddress, + MaxHops)); - TraceRouteResult traceRouteResult = new TraceRouteResult(Source, targetAddress, resolvedTargetName); + int currentHop = 1; + int timeout = TimeoutSeconds * 1000; + string hostname; - Int32 currentHop = 1; - Ping sender = new Ping(); - PingOptions pingOptions = new PingOptions(currentHop, DontFragment.IsPresent); - PingReply reply = null; - Int32 timeout = TimeoutSeconds * 1000; + var pingOptions = new PingOptions(currentHop, DontFragment.IsPresent); + + //var timer = new Stopwatch(); + PingReply reply; do { - TraceRouteReply traceRouteReply = new TraceRouteReply(); - - pingOptions.Ttl = traceRouteReply.Hop = currentHop; - currentHop++; + reply = null; + hostname = null; + pingOptions.Ttl = currentHop; - // In the specific case we don't use 'Count' property. + // We don't allow -Count parameter for -TraceRoute. // 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 = sender.Send(targetAddress, timeout, buffer, pingOptions); + reply = _sender.Send(targetAddress, timeout, buffer, pingOptions); - traceRouteReply.PingReplies.Add(reply); + if (hostname == null + && ResolveDestination + && (reply.Status == IPStatus.Success || reply.Status == IPStatus.TtlExpired)) + { + try + { + hostname = Dns.GetHostEntry(reply.Address).HostName; + } + catch + { + // Swallow hostname resolve exceptions and continue with trace + } + } + + var status = new TraceStatus( + currentHop, + new PingStatus( + Source, + hostname, + reply, + pingOptions, + reply.RoundtripTime, + buffer.Length, + i), + Source, + resolvedTargetName ?? targetNameOrAddress, + targetAddress); + + WriteObject(status); + //timer.Reset(); } catch (PingException ex) { - 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); + string message = StringUtil.Format( + TestConnectionResources.NoPingResult, + resolvedTargetName, + ex.Message); + var pingException = new PingException(message, ex.InnerException); + var errorRecord = new ErrorRecord( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + resolvedTargetName); WriteError(errorRecord); continue; } - catch - { - // Ignore host resolve exceptions. - } // We use short delay because it is impossible DoS with trace route. Thread.Sleep(200); } - if (ResolveDestination && reply.Status == IPStatus.Success) - { - traceRouteReply.ReplyRouterName = Dns.GetHostEntry(reply.Address).HostName; - } - - traceRouteReply.ReplyRouterAddress = reply.Address; - - WriteTraceRouteProgress(traceRouteReply); - - traceRouteResult.Replies.Add(traceRouteReply); - } while (reply != null && currentHop <= sMaxHops && (reply.Status == IPStatus.TtlExpired || reply.Status == IPStatus.TimedOut)); - - WriteTraceRouteFooter(); - - if (Quiet.IsPresent) - { - WriteObject(currentHop <= sMaxHops); - } - else - { - WriteObject(traceRouteResult); - } - } - - private void WriteConsoleTraceRouteHeader(string resolvedTargetName, string targetAddress) - { - _testConnectionProgressBarActivity = StringUtil.Format(TestConnectionResources.TraceRouteStart, resolvedTargetName, targetAddress, MaxHops); - - WriteInformation(_testConnectionProgressBarActivity, s_PSHostTag); - - 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) - { - string msg = string.Empty; - - 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); - } - else - { - msg = StringUtil.Format(TestConnectionResources.TraceRouteTimeOut, traceRouteReply.Hop); - } - - WriteInformation(msg, s_PSHostTag); - - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, msg); - WriteProgress(record); - } - - private void WriteTraceRouteFooter() - { - WriteInformation(TestConnectionResources.TraceRouteComplete, s_PSHostTag); - - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace); - record.RecordType = ProgressRecordType.Completed; - WriteProgress(record); - } - - /// - /// The class contains an information about a trace route attempt. - /// - public class TraceRouteReply - { - internal TraceRouteReply() - { - PingReplies = new List(DefaultTraceRoutePingCount); + currentHop++; } + while (reply != null + && currentHop <= MaxHops + && (reply.Status == IPStatus.TtlExpired || reply.Status == IPStatus.TimedOut)); - /// - /// 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; - } + WriteVerbose(TestConnectionResources.TraceRouteComplete); - /// - /// 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) + if (Quiet.IsPresent) { - Source = source; - DestinationAddress = destinationAddress; - DestinationHost = destinationHost; - Replies = new List(); + WriteObject(currentHop <= MaxHops); } - - /// - /// 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 - private void ProcessMTUSize(String targetNameOrAddress) + private void ProcessMTUSize(string targetNameOrAddress) { PingReply reply, replyResult = null; - string resolvedTargetName = null; - IPAddress targetAddress = null; - - if (!InitProcessPing(targetNameOrAddress, out resolvedTargetName, out targetAddress)) + if (!InitProcessPing(targetNameOrAddress, out _, out IPAddress targetAddress)) { return; } - WriteMTUSizeHeader(resolvedTargetName, targetAddress.ToString()); - - // 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; - Int32 timeout = TimeoutSeconds * 1000; + int timeout = TimeoutSeconds * 1000; try { - Ping sender = new Ping(); - PingOptions pingOptions = new PingOptions(MaxHops, true); + var pingOptions = new PingOptions(MaxHops, true); int retry = 1; + int pingCount = 0; while (LowMTUSize < (HighMTUSize - 1)) { + pingCount++; byte[] buffer = GetSendBuffer(CurrentMTUSize); - WriteMTUSizeProgress(CurrentMTUSize, retry); - - WriteDebug(StringUtil.Format("LowMTUSize: {0}, CurrentMTUSize: {1}, HighMTUSize: {2}", LowMTUSize, CurrentMTUSize, HighMTUSize)); + WriteVerbose(StringUtil.Format( + TestConnectionResources.MtuSizeDetectDebug, + LowMTUSize, + 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) @@ -562,14 +426,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()); - Exception pingException = new System.Net.NetworkInformation.PingException(message); - ErrorRecord errorRecord = new ErrorRecord(pingException, - TestConnectionExceptionId, - ErrorCategory.ResourceUnavailable, - targetAddress); + string message = StringUtil.Format( + TestConnectionResources.NoPingResult, + targetAddress, + reply.Status.ToString()); + var pingException = new PingException(message); + var errorRecord = new ErrorRecord( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + targetAddress); WriteError(errorRecord); return; } @@ -589,129 +455,86 @@ 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); - ErrorRecord errorRecord = new ErrorRecord(pingException, - TestConnectionExceptionId, - ErrorCategory.ResourceUnavailable, - targetAddress); + var pingException = new PingException(message, ex.InnerException); + var errorRecord = new ErrorRecord( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + targetAddress); WriteError(errorRecord); return; } - WriteMTUSizeFooter(); - if (Quiet.IsPresent) { WriteObject(CurrentMTUSize); } 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, targetNameOrAddress, replyResult)); } } - 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() - { - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace); - record.RecordType = ProgressRecordType.Completed; - WriteProgress(record); - } - #endregion MTUSizeTest #region PingTest - private void ProcessPing(String targetNameOrAddress) + private void ProcessPing(string targetNameOrAddress) { - string resolvedTargetName = null; - IPAddress targetAddress = null; - - if (!InitProcessPing(targetNameOrAddress, out resolvedTargetName, out targetAddress)) + if (!InitProcessPing(targetNameOrAddress, out string resolvedTargetName, out IPAddress targetAddress)) { return; } - if (!Continues.IsPresent) + if (!Repeat.IsPresent) { - WritePingHeader(resolvedTargetName, targetAddress.ToString()); + WriteVerbose(StringUtil.Format( + TestConnectionResources.PingStart, + resolvedTargetName, + targetAddress, + BufferSize)); } bool quietResult = true; byte[] buffer = GetSendBuffer(BufferSize); - Ping sender = new Ping(); - PingOptions pingOptions = new PingOptions(MaxHops, DontFragment.IsPresent); - PingReply reply = null; - PingReport pingReport = new PingReport(Source, resolvedTargetName); - Int32 timeout = TimeoutSeconds * 1000; - Int32 delay = Delay * 1000; + PingReply reply; + var pingOptions = new PingOptions(MaxHops, DontFragment.IsPresent); + int timeout = TimeoutSeconds * 1000; + int delay = Delay * 1000; - for (int i = 1; i <= Count; i++) + for (uint i = 1; i <= Count; i++) { try { - reply = sender.Send(targetAddress, timeout, buffer, pingOptions); + reply = _sender.Send(targetAddress, timeout, buffer, pingOptions); } catch (PingException ex) { - 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); + string message = StringUtil.Format( + TestConnectionResources.NoPingResult, + resolvedTargetName, + ex.Message); + var pingException = new PingException(message, ex.InnerException); + var errorRecord = new ErrorRecord( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + resolvedTargetName); WriteError(errorRecord); quietResult = false; continue; } - if (Continues.IsPresent) + if (Quiet.IsPresent) { - WriteObject(reply); + // 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 - { - pingReport.Replies.Add(reply); - } - - WritePingProgress(reply); + WriteObject(new PingStatus(Source, resolvedTargetName, reply, i)); } // Delay between ping but not after last ping. @@ -721,103 +544,27 @@ private void ProcessPing(String targetNameOrAddress) } } - if (!Continues.IsPresent) + if (!Repeat.IsPresent) { - WritePingFooter(); + WriteVerbose(TestConnectionResources.PingComplete); } if (Quiet.IsPresent) { WriteObject(quietResult); } - else - { - WriteObject(pingReport); - } - } - - private void WritePingHeader(string resolvedTargetName, string targetAddress) - { - _testConnectionProgressBarActivity = StringUtil.Format(TestConnectionResources.MTUSizeDetectStart, - resolvedTargetName, - targetAddress, - BufferSize); - - WriteInformation(_testConnectionProgressBarActivity, s_PSHostTag); - - ProgressRecord record = new ProgressRecord(s_ProgressId, - _testConnectionProgressBarActivity, - ProgressRecordSpace); - WriteProgress(record); - } - - private void WritePingProgress(PingReply reply) - { - string msg = string.Empty; - if (reply.Status != IPStatus.Success) - { - msg = TestConnectionResources.PingTimeOut; - } - else - { - msg = StringUtil.Format(TestConnectionResources.PingReply, - reply.Address.ToString(), - reply.Buffer.Length, - reply.RoundtripTime, - reply.Options?.Ttl); - } - - WriteInformation(msg, s_PSHostTag); - - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, msg); - WriteProgress(record); - } - - private void WritePingFooter() - { - WriteInformation(TestConnectionResources.PingComplete, s_PSHostTag); - - ProgressRecord record = new ProgressRecord(s_ProgressId, _testConnectionProgressBarActivity, ProgressRecordSpace); - record.RecordType = ProgressRecordType.Completed; - 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) + 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) @@ -835,26 +582,27 @@ private bool InitProcessPing(String targetNameOrAddress, out string resolvedTarg if (ResolveDestination) { resolvedTargetName = hostEntry.HostName; - hostEntry = Dns.GetHostEntry(hostEntry.HostName); } } catch (Exception ex) { - 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); + string message = StringUtil.Format( + TestConnectionResources.NoPingResult, + resolvedTargetName, + TestConnectionResources.CannotResolveTargetName); + var pingException = new PingException(message, ex); + var errorRecord = new ErrorRecord( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + resolvedTargetName); WriteError(errorRecord); return false; } - if (IPv6 || IPv4) + if (IPv6.IsPresent || IPv4.IsPresent) { - AddressFamily addressFamily = IPv6 ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork; + var addressFamily = IPv6 ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork; foreach (var address in hostEntry.AddressList) { @@ -867,14 +615,16 @@ private bool InitProcessPing(String targetNameOrAddress, out string resolvedTarg if (targetAddress == null) { - 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); + string message = StringUtil.Format( + TestConnectionResources.NoPingResult, + resolvedTargetName, + TestConnectionResources.TargetAddressAbsent); + var pingException = new PingException(message, null); + var errorRecord = new ErrorRecord( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + resolvedTargetName); WriteError(errorRecord); return false; } @@ -912,6 +662,232 @@ private byte[] GetSendBuffer(int bufferSize) return sendBuffer; } + /// + /// IDisposable implementation. + /// + public void Dispose() + { + _sender?.Dispose(); + } + + /// + /// 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 pings report either Success or TtlExpired. + /// + 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; } + } + + /// + /// 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; } + } + // Count of pings sent per each trace route hop. // Default = 3 (from Windows). // If we change 'DefaultTraceRoutePingCount' we should change 'ConsoleTraceRouteReply' resource string. @@ -921,12 +897,6 @@ private byte[] GetSendBuffer(int bufferSize) private const int DefaultSendBufferSize = 32; private static byte[] s_DefaultSendBuffer = null; - // 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"; } } diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/TestConnectionResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/TestConnectionResources.resx index 794680166cc..6dc729dc250 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/TestConnectionResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/TestConnectionResources.resx @@ -120,26 +120,14 @@ 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} + + LowMTUSize: {0}, CurrentMTUSize: {1}, HighMTUSize: {2} Testing connection to computer '{0}' failed: {1} @@ -153,12 +141,6 @@ 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/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs index 35d34c60670..a7f793972b7 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,122 @@ 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()); + + yield return new ExtendedTypeDefinition( + "Microsoft.PowerShell.Commands.TestConnectionCommand+TraceStatus", + ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_TraceStatus()); + } + + 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()); } private static IEnumerable ViewsOf_System_RuntimeType() diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Test-Connection.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Test-Connection.Tests.ps1 index a4a2c14433f..9b592061f64 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Test-Connection.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Test-Connection.Tests.ps1 @@ -5,11 +5,6 @@ Import-Module HelpersCommon Describe "Test-Connection" -tags "CI" { BeforeAll { - $oldInformationPreference = $InformationPreference - $oldProgressPreference = $ProgressPreference - $InformationPreference = "Ignore" - $ProgressPreference = "SilentlyContinue" - $hostName = [System.Net.Dns]::GetHostName() $targetName = "localhost" $targetAddress = "127.0.0.1" @@ -22,36 +17,31 @@ Describe "Test-Connection" -tags "CI" { # this resolves to an actual IP rather than 127.0.0.1 # this can also include both IPv4 and IPv6, so select InterNetwork rather than InterNetworkV6 $realAddress = [System.Net.Dns]::GetHostEntry($hostName).AddressList | - Where-Object {$_.AddressFamily -eq "InterNetwork"} | + Where-Object { $_.AddressFamily -eq "InterNetwork" } | Select-Object -First 1 | - Foreach-Object {$_.IPAddressToString} + ForEach-Object { $_.IPAddressToString } # under some environments, we can't round trip this and retrieve the real name from the address # in this case we will simply use the hostname - $jobContinues = Start-Job { Test-Connection $using:targetAddress -Continues } - } - - AfterAll { - $InformationPreference = $oldInformationPreference - $ProgressPreference = $oldProgressPreference + $jobContinues = Start-Job { Test-Connection $using:targetAddress -Repeat } } Context "Ping" { It "Default parameter set is 'Ping'" { $result = Test-Connection $targetName - $replies = $result.Replies - $result.Count | Should -Be 1 - $result[0] | Should -BeOfType "Microsoft.PowerShell.Commands.TestConnectionCommand+PingReport" - $result[0].Source | Should -BeExactly $hostName + $result.Count | Should -Be 4 + $result[0] | Should -BeOfType "Microsoft.PowerShell.Commands.TestConnectionCommand+PingStatus" + $result[0].Ping | Should -Be 1 + $result[0].Source | Should -BeExactly $hostName $result[0].Destination | Should -BeExactly $targetName - - $replies.Count | Should -Be 4 - $replies[0] | Should -BeOfType "System.Net.NetworkInformation.PingReply" - $replies[0].Address | Should -BeExactly $targetAddressIPv6 - $replies[0].Status | Should -BeExactly "Success" + $result[0].Address | Should -BeExactly $targetAddressIPv6 + $result[0].Status | Should -BeExactly "Success" + $result[0].Latency | Should -BeOfType "long" + $result[0].Reply | Should -BeOfType "System.Net.NetworkInformation.PingReply" + $result[0].Options | Should -BeOfType "System.Net.NetworkInformation.PingOptions" # TODO: Here and below we skip the check on Unix because .Net Core issue if ($isWindows) { - $replies[0].Buffer.Count | Should -Be 32 + $replies[0].BufferSize | Should -Be 32 } } @@ -60,8 +50,8 @@ Describe "Test-Connection" -tags "CI" { $result1 = Test-Connection -Ping $targetName -Count 1 $result2 = Test-Connection $targetName -Count 2 - $result1.Replies.Count | Should -Be 1 - $result2.Replies.Count | Should -Be 2 + $result1.Count | Should -Be 1 + $result2.Count | Should -Be 2 } It "Quiet works" { @@ -75,11 +65,13 @@ Describe "Test-Connection" -tags "CI" { It "Ping fake host" { - { $result = Test-Connection "fakeHost" -Count 1 -Quiet -ErrorAction Stop } | Should -Throw -ErrorId "TestConnectionException,Microsoft.PowerShell.Commands.TestConnectionCommand" + { $result = Test-Connection "fakeHost" -Count 1 -Quiet -ErrorAction Stop } | + Should -Throw -ErrorId "TestConnectionException,Microsoft.PowerShell.Commands.TestConnectionCommand" # Error code = 11001 - Host not found. if (!$isWindows) { $Error[0].Exception.InnerException.ErrorCode | Should -Be -131073 - } else { + } + else { $Error[0].Exception.InnerException.ErrorCode | Should -Be 11001 } } @@ -88,10 +80,10 @@ Describe "Test-Connection" -tags "CI" { It "Force IPv4 with implicit PingOptions" { $result = Test-Connection $hostName -Count 1 -IPv4 - $result.Replies[0].Address | Should -BeExactly $realAddress - $result.Replies[0].Options.Ttl | Should -BeLessOrEqual 128 + $result[0].Address | Should -BeExactly $realAddress + $result[0].Options.Ttl | Should -BeLessOrEqual 128 if ($isWindows) { - $result.Replies[0].Options.DontFragment | Should -BeFalse + $result[0].Options.DontFragment | Should -BeFalse } } @@ -103,48 +95,53 @@ Describe "Test-Connection" -tags "CI" { # it's more about breaking out of the loop $result2 = Test-Connection 8.8.8.8 -Count 1 -IPv4 -MaxHops 1 -DontFragment - $result1.Replies[0].Address | Should -BeExactly $realAddress + $result1[0].Address | Should -BeExactly $realAddress # .Net Core (.Net Framework) returns Options based on default PingOptions() constructor (Ttl=128, DontFragment = false). # After .Net Core fix we should have 'DontFragment | Should -Be $true' here. - $result1.Replies[0].Options.Ttl | Should -BeLessOrEqual 128 + $result1[0].Options.Ttl | Should -BeLessOrEqual 128 if (!$isWindows) { - $result1.Replies[0].Options.DontFragment | Should -BeNullOrEmpty - # depending on the network configuration any of the following should be returned - $result2.Replies[0].Status | Should -BeIn "TtlExpired","TimedOut","Success" - } else { - $result1.Replies[0].Options.DontFragment | Should -BeFalse + $result1[0].Options.DontFragment | Should -BeNullOrEmpty + # Depending on the network configuration any of the following should be returned + $result2[0].Status | Should -BeIn "TtlExpired", "TimedOut", "Success" + } + else { + $result1[0].Options.DontFragment | Should -BeFalse # We expect 'TtlExpired' but if a router don't reply we get `TimedOut` # AzPipelines returns $null - $result2.Replies[0].Status | Should -BeIn "TtlExpired","TimedOut",$null + $result2[0].Status | Should -BeIn "TtlExpired", "TimedOut", $null } } It "Force IPv6" -Pending { $result = Test-Connection $targetName -Count 1 -IPv6 - $result.Replies[0].Address | Should -BeExactly $targetAddressIPv6 + $result[0].Address | Should -BeExactly $targetAddressIPv6 # We should check Null not Empty! - $result.Replies[0].Options | Should -Be $null + $result[0].Options | Should -Be $null } It "MaxHops Should -Be greater 0" { - { Test-Connection $targetName -MaxHops 0 } | Should -Throw -ErrorId "System.ArgumentOutOfRangeException,Microsoft.PowerShell.Commands.TestConnectionCommand" - { Test-Connection $targetName -MaxHops -1 } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand" + { Test-Connection $targetName -MaxHops 0 } | + Should -Throw -ErrorId "System.ArgumentOutOfRangeException,Microsoft.PowerShell.Commands.TestConnectionCommand" + { Test-Connection $targetName -MaxHops -1 } | + Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand" } It "Count Should -Be greater 0" { - { Test-Connection $targetName -Count 0 } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand" + { Test-Connection $targetName -Count 0 } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand" { Test-Connection $targetName -Count -1 } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand" } It "Delay Should -Be greater 0" { - { Test-Connection $targetName -Delay 0 } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand" - { Test-Connection $targetName -Delay -1 } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand" + { Test-Connection $targetName -Delay 0 } | + Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand" + { Test-Connection $targetName -Delay -1 } | + Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand" } It "Delay works" { - $result1 = measure-command {Test-Connection localhost -Count 2} - $result2 = measure-command {Test-Connection localhost -Delay 4 -Count 2} + $result1 = Measure-Command { Test-Connection localhost -Count 2 } + $result2 = Measure-Command { Test-Connection localhost -Delay 4 -Count 2 } $result1.TotalSeconds | Should -BeGreaterThan 1 $result1.TotalSeconds | Should -BeLessThan 3 @@ -152,16 +149,18 @@ Describe "Test-Connection" -tags "CI" { } It "BufferSize Should -Be between 0 and 65500" { - { Test-Connection $targetName -BufferSize 0 } | Should Not Throw - { Test-Connection $targetName -BufferSize -1 } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand" - { Test-Connection $targetName -BufferSize 65501 } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand" + { Test-Connection $targetName -BufferSize 0 } | Should -Not Throw + { Test-Connection $targetName -BufferSize -1 } | + Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand" + { Test-Connection $targetName -BufferSize 65501 } | + Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand" } It "BufferSize works" -Pending:(!$IsWindows) { $result = Test-Connection $targetName -Count 1 -BufferSize 2 if ($isWindows) { - $result.Replies[0].Buffer.Count | Should -Be 2 + $result.BufferSize | Should -Be 2 } } @@ -170,7 +169,7 @@ Describe "Test-Connection" -tags "CI" { $resolvedName = [System.Net.DNS]::GetHostEntry($targetAddress).HostName $result.Destination | Should -BeExactly $resolvedName - $result.Replies[0].Address | Should -BeExactly $targetAddress + $result.Address | Should -BeExactly $targetAddress } It "ResolveDestination for name" { @@ -182,42 +181,44 @@ Describe "Test-Connection" -tags "CI" { $resolvedAddress = ([System.Net.DNS]::GetHostAddresses($resolvedName)[0] -split "%")[0] $result.Destination | Should -BeExactly $resolvedName - $result.Replies[0].Address | Should -BeExactly $resolvedAddress + $result.Address | Should -BeExactly $resolvedAddress } It "TimeOut works" { - (Measure-Command { Test-Connection $UnreachableAddress -Count 1 -TimeOut 1 }).TotalSeconds | Should -BeLessThan 3 - (Measure-Command { Test-Connection $UnreachableAddress -Count 1 -TimeOut 4 }).TotalSeconds | Should -BeGreaterThan 3 + (Measure-Command { Test-Connection $UnreachableAddress -Count 1 -TimeOut 1 }).TotalSeconds | + Should -BeLessThan 3 + (Measure-Command { Test-Connection $UnreachableAddress -Count 1 -TimeOut 4 }).TotalSeconds | + Should -BeGreaterThan 3 } - It "Continues works" { - # By default we do 4 ping so for '-Continues' we expect to get >4 results. + It "Repeat works" { + # By default we do 4 ping so for '-Repeat' we expect to get >4 results. # Also we should wait >4 seconds before check results but previous tests already did the pause. $result = Receive-Job $jobContinues Remove-Job $jobContinues -Force - $result.Count | Should -BeGreaterThan 4 - $result[0].Address | Should -BeExactly $targetAddress - $result[0].Status | Should -BeExactly "Success" + $result.Count | Should -BeGreaterThan 4 + $result[0].Address | Should -BeExactly $targetAddress + $result[0].Status | Should -BeExactly "Success" if ($isWindows) { - $result[0].Buffer.Count | Should -Be 32 + $result[0].BufferSize | Should -Be 32 } } -} + } - # TODO: We skip the MTUSizeDetect tests on Unix because we expect 'TtlExpired' but get 'TimeOut' internally from .Net Core + # TODO: We skip the MTUSizeDetect tests on Unix because we expect 'PacketTooBig' but get 'TimeOut' internally from .Net Core Context "MTUSizeDetect" { It "MTUSizeDetect works" -pending:($IsMacOS) { - $result = Test-Connection $hostName -MTUSizeDetect + $result = Test-Connection $hostName -DetectMtuSize - $result | Should -BeOfType "System.Net.NetworkInformation.PingReply" + $result | Should -BeOfType "Microsoft.PowerShell.Commands.TestConnectionCommand+PingMtuStatus" $result.Destination | Should -BeExactly $hostName $result.Status | Should -BeExactly "Success" - $result.MTUSize | Should -BeGreaterThan 0 + $result.MtuSize | Should -BeGreaterThan 0 } It "Quiet works" -pending:($IsMacOS) { - $result = Test-Connection $hostName -MTUSizeDetect -Quiet + $result = Test-Connection $hostName -DetectMtuSize -Quiet $result | Should -BeOfType "Int32" $result | Should -BeGreaterThan 0 @@ -228,32 +229,24 @@ Describe "Test-Connection" -tags "CI" { It "TraceRoute works" { # real address is an ipv4 address, so force IPv4 $result = Test-Connection $hostName -TraceRoute -IPv4 - $replies = $result.Replies - # Check target host reply. - $pingReplies = $replies[-1].PingReplies - - $result.Count | Should -Be 1 - $result | Should -BeOfType "Microsoft.PowerShell.Commands.TestConnectionCommand+TraceRouteResult" - $result.Source | Should -BeExactly $hostName - $result.DestinationAddress | Should -BeExactly $realAddress - $result.DestinationHost | Should -BeExactly $hostName - - $replies.Count | Should -BeGreaterThan 0 - $replies[0] | Should -BeOfType "Microsoft.PowerShell.Commands.TestConnectionCommand+TraceRouteReply" - $replies[0].Hop | Should -Be 1 - - $pingReplies.Count | Should -Be 3 - $pingReplies[0].Address | Should -BeExactly $realAddress - $pingReplies[0].Status | Should -BeExactly "Success" + + $result[0] | Should -BeOfType "Microsoft.PowerShell.Commands.TestConnectionCommand+TraceStatus" + $result[0].Source | Should -BeExactly $hostName + $result[0].TargetAddress | Should -BeExactly $realAddress + $result[0].Target | Should -BeExactly $hostName + $result[0].Hop | Should -Be 1 + $result[0].HopAddress | Should -BeExactly $realAddress + $result[0].Status | Should -BeExactly "Success" if (!$isWindows) { - $pingReplies[0].Buffer.Count | Should -Match '^0$|^32$' - } else { - $pingReplies[0].Buffer.Count | Should -Be 32 + $result[0].Reply.Buffer.Count | Should -Match '^0$|^32$' + } + else { + $result[0].Reply.Buffer.Count | Should -Be 32 } } It "Quiet works" { - $result = Test-Connection $hostName -TraceRoute -Quiet 6> $null + $result = Test-Connection $hostName -TraceRoute -Quiet $result | Should -BeTrue } @@ -268,10 +261,10 @@ Describe "Connection" -Tag "CI", "RequireAdminOnWindows" { } It "Test connection to local host port 80" { - Test-Connection '127.0.0.1' -TCPPort $WebListener.HttpPort | Should -BeTrue + Test-Connection '127.0.0.1' -TcpPort $WebListener.HttpPort | Should -BeTrue } It "Test connection to unreachable host port 80" { - Test-Connection $UnreachableAddress -TCPPort 80 -TimeOut 1 | Should -BeFalse + Test-Connection $UnreachableAddress -TcpPort 80 -TimeOut 1 | Should -BeFalse } }