mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Test-Connection: Increase output detail when performing a tcp test (#11452)
* Added verbose TCP test logic * Cleaned up output, removed source IP address * Consolidated detailed output logic into ProcessConnectionByTCPPort() * Updated existing tests with -Quiet switch * Improved output formatting, changed logic to match * Error handling and logic improvements * Updated tests to match new output * Updated logic, tcp tests now run once by default * Moved TcpConnectionTestResult enum into TcpTestStatus class * Removed unnecessary whitespace * Wrapped TcpClient() in Using statement * Appended missing period on comments * Change TcpTestStatus to TcpPortStatus for better clarity * Set default count back to 4 * Move stopwatch reset into finally block * Change TcpConnectionTestResult.New to TcpConnectionTestResult.None * Formatting fixes * Increase column size for Result output * Update logic for -quiet option * Change "TestNum" property to "Id" * Change Destination and DestinationAddress to Target and TargetAddress in TcpPortStatus * Implement new form of using declaration * Fix indentation * Update output, improve logic and update tests * Add CancellationToken to allow cancel before timeout * Assign testResult.Connected to client.Connected * Move TcpPortStatus object creation to after connection test * Remove delay from final connection test in loop * Move Count logic from ProcessConnectionByTCPPort into own function * Fix small formatting error * Add handling for TaskCanceledException * Remove redundant CancellationToken getter * Clean up handling of SocketException * Increase readability of final loop check * Remove unnecessary TimeSpan invocation * Rename cancellationTokenSource to match convention * Move SetCountForTcpTest() into BeginProcessing() * Added nameof to Count check * Removed explicit type declaration for TcpClient * Move delay and timeout evaluation out of the cycle * Remove redundant CancellationTokenSource * Initialize detailed SwitchParameter * Add check for Detailed parameter * Remove redundant tests for non-detailed output * Add return after first boolean output * Update tests with new detailed parameter set --------- Co-authored-by: Travis Plunk <travis.plunk@microsoft.com>
This commit is contained in:
co-authored by
Travis Plunk
parent
a63487950f
commit
698742ea9f
+159
-25
@@ -27,6 +27,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
[OutputType(typeof(PingMtuStatus), ParameterSetName = new string[] { MtuSizeDetectParameterSet })]
|
||||
[OutputType(typeof(int), ParameterSetName = new string[] { MtuSizeDetectParameterSet })]
|
||||
[OutputType(typeof(TraceStatus), ParameterSetName = new string[] { TraceRouteParameterSet })]
|
||||
[OutputType(typeof(TcpPortStatus), ParameterSetName = new string[] { TcpPortParameterSet })]
|
||||
public class TestConnectionCommand : PSCmdlet, IDisposable
|
||||
{
|
||||
#region Parameter Set Names
|
||||
@@ -134,6 +135,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// The default (from Windows) is 4 times.
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = DefaultPingParameterSet)]
|
||||
[Parameter(ParameterSetName = TcpPortParameterSet)]
|
||||
[ValidateRange(ValidateRangeKind.Positive)]
|
||||
public int Count { get; set; } = 4;
|
||||
|
||||
@@ -143,6 +145,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = DefaultPingParameterSet)]
|
||||
[Parameter(ParameterSetName = RepeatPingParameterSet)]
|
||||
[Parameter(ParameterSetName = TcpPortParameterSet)]
|
||||
[ValidateRange(ValidateRangeKind.Positive)]
|
||||
public int Delay { get; set; } = 1;
|
||||
|
||||
@@ -169,6 +172,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// Gets or sets whether to continue pinging until user presses Ctrl-C (or Int.MaxValue threshold reached).
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ParameterSetName = RepeatPingParameterSet)]
|
||||
[Parameter(ParameterSetName = TcpPortParameterSet)]
|
||||
[Alias("Continuous")]
|
||||
public SwitchParameter Repeat { get; set; }
|
||||
|
||||
@@ -180,6 +184,13 @@ namespace Microsoft.PowerShell.Commands
|
||||
[Parameter]
|
||||
public SwitchParameter Quiet { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to enable detailed output mode while running a TCP connection test.
|
||||
/// Without this flag, the TCP test will return a boolean result.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public SwitchParameter Detailed;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
@@ -227,6 +238,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
|
||||
/// <summary>
|
||||
/// BeginProcessing implementation for TestConnectionCommand.
|
||||
/// Sets Count for different types of tests unless specified explicitly.
|
||||
/// </summary>
|
||||
protected override void BeginProcessing()
|
||||
{
|
||||
@@ -235,6 +247,9 @@ namespace Microsoft.PowerShell.Commands
|
||||
case RepeatPingParameterSet:
|
||||
Count = int.MaxValue;
|
||||
break;
|
||||
case TcpPortParameterSet:
|
||||
SetCountForTcpTest();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,6 +296,18 @@ namespace Microsoft.PowerShell.Commands
|
||||
|
||||
#region ConnectionTest
|
||||
|
||||
private void SetCountForTcpTest()
|
||||
{
|
||||
if (Repeat.IsPresent)
|
||||
{
|
||||
Count = int.MaxValue;
|
||||
}
|
||||
else if (!MyInvocation.BoundParameters.ContainsKey(nameof(Count)))
|
||||
{
|
||||
Count = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessConnectionByTCPPort(string targetNameOrAddress)
|
||||
{
|
||||
if (!TryResolveNameOrAddress(targetNameOrAddress, out _, out IPAddress? targetAddress))
|
||||
@@ -293,42 +320,80 @@ namespace Microsoft.PowerShell.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
TcpClient client = new();
|
||||
int timeoutMilliseconds = TimeoutSeconds * 1000;
|
||||
int delayMilliseconds = Delay * 1000;
|
||||
|
||||
try
|
||||
for (var i = 1; i <= Count; i++)
|
||||
{
|
||||
Task connectionTask = client.ConnectAsync(targetAddress, TcpPort);
|
||||
string targetString = targetAddress.ToString();
|
||||
long latency = 0;
|
||||
SocketError status = SocketError.SocketError;
|
||||
|
||||
for (var i = 1; i <= TimeoutSeconds; i++)
|
||||
Stopwatch stopwatch = new Stopwatch();
|
||||
|
||||
using var client = new TcpClient();
|
||||
|
||||
try
|
||||
{
|
||||
Task timeoutTask = Task.Delay(millisecondsDelay: 1000);
|
||||
Task.WhenAny(connectionTask, timeoutTask).Result.Wait();
|
||||
stopwatch.Start();
|
||||
|
||||
if (timeoutTask.Status == TaskStatus.Faulted || timeoutTask.Status == TaskStatus.Canceled)
|
||||
if (client.ConnectAsync(targetAddress, TcpPort).Wait(timeoutMilliseconds, _dnsLookupCancel.Token))
|
||||
{
|
||||
// Waiting is interrupted by Ctrl-C.
|
||||
WriteObject(false);
|
||||
return;
|
||||
latency = stopwatch.ElapsedMilliseconds;
|
||||
status = SocketError.Success;
|
||||
}
|
||||
|
||||
if (connectionTask.Status == TaskStatus.RanToCompletion)
|
||||
else
|
||||
{
|
||||
WriteObject(true);
|
||||
return;
|
||||
status = SocketError.TimedOut;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Silently ignore connection errors.
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.Close();
|
||||
}
|
||||
catch (AggregateException ae)
|
||||
{
|
||||
ae.Handle((ex) =>
|
||||
{
|
||||
if (ex is TaskCanceledException)
|
||||
{
|
||||
throw new PipelineStoppedException();
|
||||
}
|
||||
if (ex is SocketException socketException)
|
||||
{
|
||||
status = socketException.SocketErrorCode;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
stopwatch.Reset();
|
||||
}
|
||||
|
||||
WriteObject(false);
|
||||
if (!Detailed.IsPresent)
|
||||
{
|
||||
WriteObject(status == SocketError.Success);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteObject(new TcpPortStatus(
|
||||
i,
|
||||
Source,
|
||||
targetNameOrAddress,
|
||||
targetAddress,
|
||||
TcpPort,
|
||||
latency,
|
||||
status == SocketError.Success,
|
||||
status
|
||||
));
|
||||
}
|
||||
|
||||
if (i < Count)
|
||||
{
|
||||
Task.Delay(delayMilliseconds).Wait(_dnsLookupCancel.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion ConnectionTest
|
||||
@@ -877,6 +942,75 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The class contains information about the TCP connection test.
|
||||
/// </summary>
|
||||
public class TcpPortStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TcpPortStatus"/> class.
|
||||
/// </summary>
|
||||
/// <param name="id">The number of this test.</param>
|
||||
/// <param name="source">The source machine name or IP of the test.</param>
|
||||
/// <param name="target">The target machine name or IP of the test.</param>
|
||||
/// <param name="targetAddress">The resolved IP from the target.</param>
|
||||
/// <param name="port">The port used for the connection.</param>
|
||||
/// <param name="latency">The latency of the test.</param>
|
||||
/// <param name="connected">If the test connection succeeded.</param>
|
||||
/// <param name="status">Status of the underlying socket.</param>
|
||||
internal TcpPortStatus(int id, string source, string target, IPAddress targetAddress, int port, long latency, bool connected, SocketError status)
|
||||
{
|
||||
Id = id;
|
||||
Source = source;
|
||||
Target = target;
|
||||
TargetAddress = targetAddress;
|
||||
Port = port;
|
||||
Latency = latency;
|
||||
Connected = connected;
|
||||
Status = status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets the count of the test.
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source from which the test was sent.
|
||||
/// </summary>
|
||||
public string Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target name.
|
||||
/// </summary>
|
||||
public string Target { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the resolved address for the target.
|
||||
/// </summary>
|
||||
public IPAddress TargetAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the port used for the test.
|
||||
/// </summary>
|
||||
public int Port { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the latancy of the connection.
|
||||
/// </summary>
|
||||
public long Latency { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the result of the test.
|
||||
/// </summary>
|
||||
public bool Connected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the state of the socket after the test.
|
||||
/// </summary>
|
||||
public SocketError Status { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The class contains information about the source, the destination and ping results.
|
||||
/// </summary>
|
||||
|
||||
+29
@@ -252,6 +252,10 @@ namespace System.Management.Automation.Runspaces
|
||||
"Microsoft.PowerShell.MarkdownRender.PSMarkdownOptionInfo",
|
||||
ViewsOf_Microsoft_PowerShell_MarkdownRender_MarkdownOptionInfo());
|
||||
|
||||
yield return new ExtendedTypeDefinition(
|
||||
"Microsoft.PowerShell.Commands.TestConnectionCommand+TcpPortStatus",
|
||||
ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_TcpPortStatus());
|
||||
|
||||
yield return new ExtendedTypeDefinition(
|
||||
"Microsoft.PowerShell.Commands.TestConnectionCommand+PingStatus",
|
||||
ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingStatus());
|
||||
@@ -1916,6 +1920,31 @@ namespace System.Management.Automation.Runspaces
|
||||
.EndList());
|
||||
}
|
||||
|
||||
private static IEnumerable<FormatViewDefinition> ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_TcpPortStatus()
|
||||
{
|
||||
yield return new FormatViewDefinition(
|
||||
"Microsoft.PowerShell.Commands.TestConnectionCommand+TcpPortStatus",
|
||||
TableControl.Create()
|
||||
.AddHeader(Alignment.Right, label: "Id", width: 4)
|
||||
.AddHeader(Alignment.Left, label: "Source", width: 16)
|
||||
.AddHeader(Alignment.Left, label: "Address", width: 25)
|
||||
.AddHeader(Alignment.Right, label: "Port", width: 7)
|
||||
.AddHeader(Alignment.Right, label: "Latency(ms)", width: 7)
|
||||
.AddHeader(Alignment.Left, label: "Connected", width: 10)
|
||||
.AddHeader(Alignment.Left, label: "Status", width: 24)
|
||||
.StartRowDefinition()
|
||||
.AddPropertyColumn("Id")
|
||||
.AddPropertyColumn("Source")
|
||||
.AddPropertyColumn("TargetAddress")
|
||||
.AddPropertyColumn("Port")
|
||||
.AddPropertyColumn("Latency")
|
||||
.AddPropertyColumn("Connected")
|
||||
.AddPropertyColumn("Status")
|
||||
.EndRowDefinition()
|
||||
.GroupByProperty("Target")
|
||||
.EndTable());
|
||||
}
|
||||
|
||||
private static IEnumerable<FormatViewDefinition> ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingStatus()
|
||||
{
|
||||
yield return new FormatViewDefinition(
|
||||
|
||||
@@ -321,13 +321,49 @@ Describe "Connection" -Tag "CI", "RequireAdminOnWindows" {
|
||||
$UnreachableAddress = "10.11.12.13"
|
||||
}
|
||||
|
||||
It "Test connection to local host port 80" {
|
||||
It "Test connection to local host on working port" {
|
||||
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
|
||||
}
|
||||
|
||||
It "Test detailed connection to local host on working port" {
|
||||
$result = Test-Connection '127.0.0.1' -TcpPort $WebListener.HttpPort -Detailed
|
||||
|
||||
$result.Count | Should -Be 1
|
||||
$result[0].Id | Should -BeExactly 1
|
||||
$result[0].TargetAddress | Should -BeExactly '127.0.0.1'
|
||||
$result[0].Port | Should -Be $WebListener.HttpPort
|
||||
$result[0].Latency | Should -BeGreaterOrEqual 0
|
||||
$result[0].Connected | Should -BeTrue
|
||||
$result[0].Status | Should -BeExactly 'Success'
|
||||
}
|
||||
|
||||
It "Test detailed connection to local host on working port with modified count" {
|
||||
$result = Test-Connection '127.0.0.1' -TcpPort $WebListener.HttpPort -Detailed -Count 2
|
||||
|
||||
$result.Count | Should -Be 2
|
||||
$result[0].Id | Should -BeExactly 1
|
||||
$result[0].TargetAddress | Should -BeExactly '127.0.0.1'
|
||||
$result[0].Port | Should -Be $WebListener.HttpPort
|
||||
$result[0].Latency | Should -BeGreaterOrEqual 0
|
||||
$result[0].Connected | Should -BeTrue
|
||||
$result[0].Status | Should -BeExactly 'Success'
|
||||
}
|
||||
|
||||
It "Test detailed connection to unreachable host port 80" {
|
||||
$result = Test-Connection $UnreachableAddress -TcpPort 80 -Detailed -TimeOut 1
|
||||
|
||||
$result.Count | Should -Be 1
|
||||
$result[0].Id | Should -BeExactly 1
|
||||
$result[0].TargetAddress | Should -BeExactly $UnreachableAddress
|
||||
$result[0].Port | Should -Be 80
|
||||
$result[0].Latency | Should -BeExactly 0
|
||||
$result[0].Connected | Should -BeFalse
|
||||
$result[0].Status | Should -Not -BeExactly 'Success'
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Test-Connection should run in the default synchronization context (threadpool)" -Tag "CI" {
|
||||
|
||||
Reference in New Issue
Block a user