PSTcpIp.psm1

#--------------------------------------------------------------------------------------------------------------------------------------------
# NAME: TcpIp module
#
# FILE NAME: PSTcpIp.psm1
#
# PURPOSE: Provides cmdlets to perform various TCPIP related tasks.
#
# Test-TcpConnection
#--------------------------------------------------------------------------------------------------------------------------------------------

using namespace System
using namespace System.Net
using namespace System.Collections.Generic


#region Load format file

$formatFilePath = Join-Path -Path $PSScriptRoot -ChildPath 'TcpConnectionStatus.Format.PS1Xml'

if (Test-Path -Path $PSScriptRoot) { Update-FormatData -PrependPath $formatFilePath }

#endregion


#region Load config data

$jsonFilePath  = Join-Path -Path $PSScriptRoot -ChildPath 'ConfigData\TcpPorts.json'

if (-not(Test-Path -Path $jsonFilePath))
{
    $FileNotFoundException = New-Object -TypeName System.IO.FileNotFoundException -ArgumentList ("JSON configuration file not found in the following path: {0}" -f $jsonFilePath)
    throw $FileNotFoundException
}

$tcpPortData = Get-Content -Path $jsonFilePath -Raw | ConvertFrom-Json

$portTable = @{}
foreach ($entry in $tcpPortData)
{
    if (-not($portTable.ContainsKey([int]$entry.Port)))
    {
        $portTable.Add([int]$entry.port, $entry.description)
    }
}

New-Variable -Name tcpPortAndDescriptionData -Value $portTable -Option ReadOnly -Scope Global -Force

#endregion


#region Class Definitions

$tcpConnectionStatusClassDef = @"
using System;
using System.Net;
namespace PSTcpIp
{
    public class TcpConnectionStatus
    {
        public string Destination { get; set; }
        public string IPAddress { get; set; }
        public Int32 Port { get; set; }
        public string Service { get; set; }
        public bool Connected { get; set; }
        public bool HostNameResolved { get; set; }
    }
}
"@


Add-Type -TypeDefinition $tcpConnectionStatusClassDef -ReferencedAssemblies System.Net.Primitives -ErrorAction Stop

#endregion


#region Exported Functions

function Test-TcpConnection
{
  <#
    .SYNOPSIS
        Tests TCP connectivity to a remote computer.
    .DESCRIPTION
        The Test-TcpConnection function attempts to establish TCP connectivity
        to one or more remote computers.
 
        You can use the parameters of Test-TcpConnection to specify the
        receiving computers and to set the number of connection requests.
 
        Unlike the traditional ping command, Test-TcpConnection returns a PSObject
        that you can investigate in Windows PowerShell, but you can use the Quiet parameter
        to force it to return only a Boolean value.
 
        Unlike Test-Connection, Test-TcpConnection does not depend on ICMP echo requests
        to determine if connectivity can be established. This is particularly useful in
        cases where firewalls block ICMP but allow access to HTTP and SSL.
    .NOTES
        Requires PowerShell version 4 or above.
    .PARAMETER DNSHostName
        The target hostname to test TCP connectivity against.
    .PARAMETER Port
        The TCP port to test.
    .PARAMETER Count
        The amount of iterations. Default is 1.
    .PARAMETER Timeout
        The timeout value expressed in milliseconds. The default value is 1200.
    .PARAMETER Quiet
        Returns a boolean result only.
    .EXAMPLE
        Test-TcpConnection -DNSHostName 'myserver' -Port 80
 
        Tests HTTP connectivity on the server 'myserver'.
    .EXAMPLE
        Test-TcpConnection -ComputerName 'mydomaincontroller' -Port 389 -Quiet
 
        Tests LDAP connectivity on the server 'mydomaincontroller' using the parameter alias ComputerName with a boolean return value.
    .EXAMPLE
        Test-TcpConnection -DNSHostName 'mywebserver' -Port 443 -Count 12
 
        Tests SSL connectivity on the server 'mywebserver' twelve times as opposed to the default four attempts.
    .EXAMPLE
        Test-TcpConnection -IPAddress 134.170.184.133 -Port 80
 
        Tests HTTP connectivity to a host with an IPV4 address of 134.170.184.133.
    .EXAMPLE
        @((80..445), (5000..6000)) | % { $ports += $_ }
        Test-TcpConnection -ComputerName 'mywebserver' -Port $ports -Count 1 -Timeout 100
 
        Scans 'mywebserver' for TCP ports 80 through 445, and 5000 through 6000 with a 100 millisecond timeout.
    .EXAMPLE
        Test-TcpConnection -DNSHostName 'myserver'
 
        Tests TCP connectivity on the server 'myserver' against the following ports: 20, 21, 22, 23, 25, 53, 80, 88, 139, 389, 443, 445, 636, 3389, 5985
    .INPUTS
        System.String
 
            A string value received by the the DNSHostName parameter.
            This is the hostname of the computer that you wish to test TCP connectivity against.
            This parameter is also accessible by the aliases ComputerName, HostName, and IPAddress.
 
        System.Int32
 
            An integer value received by the the Port parameter.
     .OUTPUTS
        PSTcpIp.TcpConnectionStatus, System.Boolean
 
            By default this cmdlet returns a TcpConnectionStatus object. When you use the Quiet parameter, it returns a Boolean.
     .LINK
    #>

    [CmdletBinding(DefaultParameterSetName='Default')]
    [Alias('ttc')]
    [OutputType([PSTcpIp.TcpConnectionStatus], ParameterSetName='Default')]
    [OutputType([System.Boolean], ParameterSetName='Quiet')]
    param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0)][Alias('ComputerName','HostName','IPAddress','Name', 'h', 'i')][String[]]$DNSHostName,
        [Parameter(Mandatory=$false,ValueFromPipeline=$false,Position=1)][ValidateRange(1,65535)][Alias('PortNumber','p')][Int[]]$Port,
        [Parameter(Mandatory=$false,ParameterSetName='Default',Position=2)][ValidateRange(1,100000)][Alias('c')][Int]$Count=1,
        [Parameter(Mandatory=$true,ParameterSetName='Quiet',Position=2)][Alias('q')][Switch]$Quiet,
        [Parameter(Mandatory=$false,Position=3)][ValidateRange(1,2500)][Alias('to')][Int]$Timeout=1200
    )
    BEGIN
    {
        New-Variable -Name ipv4Addresses -Value $null -Force
        New-Variable -Name ipv4Address -Value $null -Force
        New-Variable -Name tcpClient -Value $null -Force

        $commonPorts = @(20, 21, 22, 23, 25, 53, 80, 88, 139, 389, 443, 445, 636, 3389, 5985)
    }
    PROCESS
    {
        $__ComputerNames = $DNSHostName

        foreach ($__ComputerName in $__ComputerNames)
        {
            if ($Quiet)
            {
                $amountOfIterations = 1
            }
            else
            {
                if ($Count -le 0)
                {
                    $amountOfIterations = 1
                }
                else
                {
                    $amountOfIterations = $Count
                }
            }

            try
            {
                $destination = [System.Net.DNS]::GetHostEntry($__ComputerName) | Select-Object -ExpandProperty HostName
            }
            catch
            {
                $destination = $__ComputerName
            }

            [boolean]$nameResolved = $false
            try
            {
                $ipv4Addresses = @(([System.Net.Dns]::GetHostAddresses($__ComputerName)).IPAddressToString)
                $ipv4Address = $ipv4Addresses[0]
                $nameResolved = $true
            }
            catch
            {
                $ipv4Address = 'Unable to resolve hostname'
                $nameResolved = $false
            }

            for ($i=1; $i -le $amountOfIterations; $i++)
            {
                [boolean]$connectionSucceeded = $false

                if (-not($PSBoundParameters.ContainsKey("Port")))
                {
                    $__PortNumbers = $commonPorts
                }
                else
                {
                    $__PortNumbers = $Port
                }

                $__PortNumbers | ForEach-Object {
                    $__PortNumber = $PSItem
                    if ($nameResolved)
                    {
                        $tcpClient = New-Object -TypeName System.Net.Sockets.TcpClient
                        try
                        {
                            ($tcpClient.BeginConnect($ipv4Address, $__PortNumber, $null, $null).AsyncWaitHandle.WaitOne($Timeout)) | Out-Null
                            if ($tcpClient.Connected -eq $true)
                            {
                                $connectionSucceeded = $true
                            }
                            else
                            {
                                $connectionSucceeded = $false
                            }
                        }
                        catch
                        {
                            $connectionSucceeded = $false
                        }
                        finally
                        {
                            $tcpClient.Close()
                            $tcpClient.Dispose()
                        }
                    }

                    $connectionStatusObject = New-Object -TypeName PSTcpIp.TcpConnectionStatus
                    $connectionStatusObject.Destination = $destination
                    $connectionStatusObject.IPAddress = $ipv4Address
                    $connectionStatusObject.Port = $__PortNumber
                    $connectionStatusObject.Service =  $tcpPortAndDescriptionData.Item($__PortNumber)
                    $connectionStatusObject.Connected = $connectionSucceeded
                    $connectionStatusObject.HostNameResolved = $nameResolved

                    if ($Quiet)
                    {
                        return $connectionSucceeded
                    }
                    else
                    {
                        return $connectionStatusObject
                    }
                }
            }
        }
    }
    END
    {
        Remove-Variable -Name tcpClient
        Remove-Variable -Name ipv4Address
        Remove-Variable -Name ipv4Addresses
    }
}

#endregion


Export-ModuleMember -Function Test-TcpConnection

New-Alias -Name ttc -Value Test-TcpConnection -Force

Export-ModuleMember -Alias ttc