class NetRouteDiagnostics
{
    #Remote hostname used for routing
    [String] $ComputerName

    #Remote IP address used for routing
    [System.Net.IPAddress] $RemoteAddress

    #Resolved IP addresses for ComputerName
    [System.Net.IPAddress[]] $ResolvedAddresses

    #SourceAddress constraint used for routing
    [System.Net.IPAddress] $ConstrainSourceAddress

    #Interface constraint used for routing
    [UInt32] $ConstrainInterfaceIndex

    #SourceAddress selected for routing
    [Microsoft.Management.Infrastructure.CimInstance] $SelectedSourceAddress

    #Outgoing interface information selected for routing
    [String] $OutgoingInterfaceAlias
    [UInt32] $OutgoingInterfaceIndex
    [String] $OutgoingInterfaceDescription
    [Microsoft.Management.Infrastructure.CimInstance] $OutgoingNetAdapter

    #NetRoute selected for routing
    [Microsoft.Management.Infrastructure.CimInstance] $SelectedNetRoute

    #Log file used for logging route events
    [String] $LogFile

    #Events logged during routing
    [String[]] $RouteSelectionEvents
    [String[]] $SourceAddressSelectionEvents
    [String[]] $DestinationAddressSelectionEvents

    #An indicator to the formatter that details should be shown
    [Bool] $Detailed

    #If diagnostics succeeded
    [Bool] $RouteDiagnosticsSucceeded
}

class TestNetConnectionResult
{
    [String] $ComputerName

    #The Remote IP address used for connectivity
    [System.Net.IPAddress] $RemoteAddress

    #Resolved IP addresses for ComputerName
    [System.Net.IPAddress[]] $ResolvedAddresses

    #Indicates if the Ping was successful
    [Bool] $PingSucceeded

    #Details of the ping
    [System.Net.NetworkInformation.PingReply] $PingReplyDetails

    #The TCP socket
    [System.Net.Sockets.Socket] $TcpClientSocket

    #If the test succeeded
    [Bool] $TcpTestSucceeded

    #Remote port used
    [UInt32] $RemotePort

    #The results of the traceroute
    [String[]] $TraceRoute

    #An indicator to the formatter that details should be shown
    [Bool] $Detailed

    #Information on the interface used for connectivity
    [String] $InterfaceAlias
    [UInt32] $InterfaceIndex
    [String] $InterfaceDescription
    [Microsoft.Management.Infrastructure.CimInstance] $NetAdapter
    [Microsoft.Management.Infrastructure.CimInstance] $NetRoute

    #Source IP address
    [Microsoft.Management.Infrastructure.CimInstance] $SourceAddress

    #DNS information
    [Bool] $NameResolutionSucceeded
    [Object] $BasicNameResolution
    [Object] $LLMNRNetbiosRecords
    [Object] $DNSOnlyRecords
    [Object] $AllNameResolutionResults

    #NetSec Info
    [Bool] $IsAdmin #If the test succeeded
    [String] $NetworkIsolationContext
    [Microsoft.Management.Infrastructure.CimInstance[]] $MatchingIPsecRules
}

function Test-NetConnection
{
    [CmdletBinding( )]
    Param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
        [Alias('RemoteAddress','cn')]
        [String] $ComputerName = "internetbeacon.msedge.net",

        [Parameter(ParameterSetName = "ICMP", Mandatory = $False)]
        [Switch] $TraceRoute,

        [Parameter(ParameterSetName = "ICMP", Mandatory = $False)]
        [ValidateRange(1,120)]
        [Int] $Hops = 30,

        [Parameter(ParameterSetName = "CommonTCPPort", Mandatory = $True, Position = 1)]
        [ValidateSet("HTTP", "RDP", "SMB", "WINRM")]
        [String] $CommonTCPPort = "",

        [Parameter(ParameterSetName = "RemotePort", Mandatory = $True, ValueFromPipelineByPropertyName = $true)]
        [Alias('RemotePort')] [ValidateRange(1,65535)]
        [Int] $Port = 0,

        [Parameter(ParameterSetName = "NetRouteDiagnostics", Mandatory = $True)]
        [Switch] $DiagnoseRouting,

        [Parameter(ParameterSetName = "NetRouteDiagnostics", Mandatory = $False)]
        [String] $ConstrainSourceAddress = "",

        [Parameter(ParameterSetName = "NetRouteDiagnostics", Mandatory = $False)]
        [UInt32] $ConstrainInterface = 0,

        [ValidateSet("Quiet", "Detailed")]
        [String] $InformationLevel = "Standard"
    )

    Begin
    {

        ##Description: Checks if the local execution context is elevated
        ##Input: None
        ##Output: Boolean. True if the local execution context is elevated.
        function CheckIfAdmin
        {
            $CurrentIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
            $CurrentSecurityPrincipal = [System.Security.Principal.WindowsPrincipal]::new($CurrentIdentity)
            $AdminPrincipal = [System.Security.Principal.WindowsBuiltInRole]::Administrator
            return $CurrentSecurityPrincipal.IsInRole($AdminPrincipal)
        }

        ##Description: Resolves a hostname.
        ##Input: The user-provided computername that will be pinged/tested
        ##Output: The resolved IP addresses for computername
        function ResolveTargetName
        {
            param ($TargetName)

            $Addresses = $null
            try
            {
                $Addresses = [System.Net.Dns]::GetHostAddressesAsync($TargetName).GetAwaiter().GetResult()
            }
            catch
            {
                Write-Debug "Name resolution of $TargetName threw exception: $($_.Exception.Message)"
            }

            if ($Addresses -eq $null)
            {
                Write-Warning "Name resolution of $TargetName failed"
            }

            return $Addresses
        }

        ##Description: Pings a specified host
        ##Input: IP address to ping
        ##Output: PingReplyDetails for the ping attempt to host
        function PingTest
        {
            param ($TargetIPAddress)

            $Ping = [System.Net.NetworkInformation.Ping]::new()
            $PingReplyDetails = $null

            ##Indeterminate progress indication
            Write-Progress  -Activity "Test-NetConnection :: $TargetIPAddress" -Status "Ping/ICMP Test" -CurrentOperation "Waiting for echo reply" -SecondsRemaining -1 -PercentComplete -1

            try
            {
                $PingReplyDetails = $Ping.SendPingAsync($TargetIPAddress).GetAwaiter().Getresult()
            }
            catch
            {
                Write-Debug "Ping to $TargetIPAddress threw exception: $($_.Exception.Message)"
            }
            finally
            {
                $Ping.Dispose()
            }

            return $PingReplyDetails
        }

        ##Description: Traces a route to a specified IP address using repetitive echo requests
        ##Input: IP address to trace against
        ##Output: Array of IP addresses representing the traced route. The message from the ping reply status is emmited, if there is no response.
        function TraceRoute
        {
            param ($TargetIPAddress,$Hops)

            $Ping = [System.Net.NetworkInformation.Ping]::new()
            $PingOptions = [System.Net.NetworkInformation.PingOptions]::new()
            $PingOptions.Ttl = 1
            [Byte[]]$DataBuffer = @()
            1..10 | foreach {$DataBuffer += [Byte]0}
            $ReturnTrace = @()

            do
            {
                try
                {
                    $CurrentHop = [int] $PingOptions.Ttl
                    write-progress -CurrentOperation "TTL = $CurrentHop" -Status "ICMP Echo Request (Max TTL = $Hops)" -Activity "TraceRoute" -PercentComplete -1 -SecondsRemaining -1
                    $PingReplyDetails = $Ping.SendPingAsync($TargetIPAddress, 4000, $DataBuffer, $PingOptions).GetAwaiter().Getresult()

                    if ($PingReplyDetails.Address -eq $null)
                    {
                        $ReturnTrace += $PingReplyDetails.Status.ToString()
                    }
                    else
                    {
                        $ReturnTrace += $PingReplyDetails.Address.IPAddressToString
                    }
                }
                catch
                {
                    Write-Debug "Ping to $TargetIPAddress threw exception: $($_.Exception.Message)"
                    $ReturnTrace += "..."
                }
                $PingOptions.Ttl++
            }
            while (($PingReplyDetails.Status -ne 'Success') -and ($PingOptions.Ttl -le $Hops))

            ##If the last entry in the trace does not equal the target, then the trace did not successfully complete
            if ($ReturnTrace[-1] -ne $TargetIPAddress)
            {
                $OutputString = "Trace route to destination " + $TargetIPAddress + " did not complete. Trace terminated :: " + $ReturnTrace[-1]
                Write-Warning $OutputString
            }

            $Ping.Dispose()
            return $ReturnTrace
        }

        ##Description: Attempts a TCP connection against a specified IP address
        ##Input: IP address and port to connect to
        ##Output: If the connection succeeded (as a boolean)
        function TestTCP
        {
            param ($TargetIPAddress,$TargetPort)

            $ProgressString = "Test-NetConnection - " + $TargetIPAddress + ":" + $TargetPort
            Write-Progress -Activity $ProgressString -Status "Attempting TCP connect" -CurrentOperation "Waiting for response" -SecondsRemaining -1 -PercentComplete -1

            $Success = $False
            $TCPClient = [System.Net.Sockets.TcpClient]::new($TargetIPAddress.AddressFamily)
            try
            {
                $null = $TCPClient.ConnectAsync($TargetIPAddress, $TargetPort).GetAwaiter().Getresult()
                $Success = $TCPClient.Connected;
            }
            catch
            {
                Write-Debug "TCP connect to ($TargetIPAddress : $TargetPort) threw exception: $($_.Exception.Message)"
            }
            finally
            {
                $TCPClient.Dispose()
            }

            return $Success
        }

        ##Description: Modifies the provided object with the correct local connectivty information
        ##Input: TestNetConnectionResults object that will be modified
        ##Output: Modified TestNetConnectionResult object
        function ResolveRoutingandAdapterWMIObjects
        {
            param ($TestNetConnectionResult)

            try
            {
                $TestNetConnectionResult.SourceAddress, $TestNetConnectionResult.NetRoute = Find-NetRoute -RemoteIPAddress $TestNetConnectionResult.RemoteAddress -ErrorAction SilentlyContinue
                $TestNetConnectionResult.NetAdapter = $TestNetConnectionResult.NetRoute | Get-NetAdapter -IncludeHidden -ErrorAction SilentlyContinue

                $TestNetConnectionResult.InterfaceAlias = $TestNetConnectionResult.NetRoute.InterfaceAlias
                $TestNetConnectionResult.InterfaceIndex = $TestNetConnectionResult.NetRoute.InterfaceIndex
                $TestNetConnectionResult.InterfaceDescription =  $TestNetConnectionResult.NetAdapter.InterfaceDescription
            }
            catch
            {
                Write-Debug "ResolveRoutingandAdapterWMIObjects threw exception: $($_.Exception.Message)"
            }
            return $TestNetConnectionResult
        }

        ##Description: Resolves the DNS details for the computername
        ##Input: The TestNetConnectionResults object that will be "filled in" with DNS information
        ##Output: The modified TestNetConnectionResults object
        function ResolveDNSDetails
        {
            param ($TestNetConnectionResult)

            $TestNetConnectionResult.DNSOnlyRecords = @( Resolve-DnsName $ComputerName -DnsOnly -NoHostsFile -Type A_AAAA -ErrorAction SilentlyContinue | where-object {($_.QueryType -eq "A") -or ($_.QueryType -eq "AAAA") -or ($_.QueryType -eq "PTR")} )
            $TestNetConnectionResult.LLMNRNetbiosRecords = @( Resolve-DnsName $ComputerName -LlmnrNetbiosOnly   -NoHostsFile -ErrorAction SilentlyContinue | where-object {($_.QueryType -eq "A") -or ($_.QueryType -eq "AAAA") } )
            $TestNetConnectionResult.BasicNameResolution = @(Resolve-DnsName $ComputerName -ErrorAction SilentlyContinue | where-object {($_.QueryType -eq "A") -or ($_.QueryType -eq "AAAA") -or ($_.QueryType -eq "PTR")} )

            $TestNetConnectionResult.AllNameResolutionResults = $Return.BasicNameResolution  + $Return.DNSOnlyRecords + $Return.LLMNRNetbiosRecords | Sort-Object -Unique -Property Address
            return $TestNetConnectionResult
        }

        ##Description: Resolves the network security details for the computername
        ##Input: The TestNetConnectionResults object that will be "filled in" with network security information
        ##Output: Teh modified TestNetConnectionResults object
        function ResolveNetworkSecurityDetails
        {
            param ($TestNetConnectionResult)

            $TestNetConnectionResult.IsAdmin  = CheckIfAdmin
            $NetworkIsolationInfo = Invoke-CimMethod -Namespace root\standardcimv2 -ClassName MSFT_NetAddressFilter -MethodName QueryIsolationType -Arguments @{InterfaceIndex = [uint32]$TestNetConnectionResult.InterfaceIndex; RemoteAddress = [string]$TestNetConnectionResult.RemoteAddress} -ErrorAction SilentlyContinue

            switch ($NetworkIsolationInfo.IsolationType)
            {
                1 {$TestNetConnectionResult.NetworkIsolationContext = "Private Network";}
                0 {$TestNetConnectionResult.NetworkIsolationContext = "Loopback";}
                2 {$TestNetConnectionResult.NetworkIsolationContext = "Internet";}
            }

            ##Elevation is required to read IPsec information for the connection.
            if ($TestNetConnectionResult.IsAdmin)
            {
                $TestNetConnectionResult.MatchingIPsecRules = Find-NetIPsecRule -RemoteAddress $TestNetConnectionResult.RemoteAddress  -RemotePort $TestNetConnectionResult.RemotePort -Protocol TCP -ErrorAction SilentlyContinue
            }

            return $TestNetConnectionResult
        }

        ##Description: Diagnose route selection for a destination
        ##Input: RouteDiagnosticsResults object that will be filled on with route diagnostics information.
        ##Output: None
        function DiagnoseRouteSelection
        {
            param ([NetRouteDiagnostics] $RouteDiagnostics)

            $RouteDiagnostics.RouteDiagnosticsSucceeded = $False

            if ($RouteDiagnostics.Detailed)
            {
                Write-Progress  -Activity "Test-NetConnection :: $($RouteDiagnostics.RemoteAddress)" -Status "RouteDiagnostics" -CurrentOperation "Starting Route Event Tracing" -SecondsRemaining -1 -PercentComplete -1

                $LogFile = ""
                do
                {
                    $LogFile = [System.IO.Path]::GetTempFileName().split(".")[0] + "Test-NetConnection.etl"
                }
                while (Test-Path -Path $LogFile -ErrorAction SilentlyContinue)

                $TraceResults = netsh trace start tracefile=$LogFile provider=Microsoft-Windows-TCPIP keywords=ut:TcpipRoute report=di perfmerge=no correlation=di session=tnc
            }

            Write-Progress  -Activity "Test-NetConnection :: $($RouteDiagnostics.ComputerName)" -Status "RouteDiagnostics" -CurrentOperation "Resolving name" -SecondsRemaining -1 -PercentComplete -1

            $RouteDiagnostics.ResolvedAddresses = ResolveTargetName -TargetName $RouteDiagnostics.ComputerName
            if ($RouteDiagnostics.ResolvedAddresses -eq $null)
            {
                netsh trace stop sessionname=tnc | out-null
                return
            }

            $RouteDiagnostics.RemoteAddress = $RouteDiagnostics.ResolvedAddresses[0]

            if ($RouteDiagnostics.ConstrainSourceAddress -eq $null)
            {
                if ($RouteDiagnostics.RemoteAddress.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork)
                {
                    $RouteDiagnostics.ConstrainSourceAddress = [System.Net.IPAddress]::Any
                }
                else
                {
                    $RouteDiagnostics.ConstrainSourceAddress = [System.Net.IPAddress]::IPv6Any
                }
            }

            if ($RouteDiagnostics.Detailed -and (Test-Path -Path $LogFile))
            {
                ##Flush the destination cache to trigger a new route route lookup
                if ($RouteDiagnostics.RemoteAddress.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork)
                {
                    netsh int ipv4 delete destinationcache | out-null
                }
                else
                {
                    netsh int ipv6 delete destinationcache | out-null
                }
            }

            Write-Progress  -Activity "Test-NetConnection :: $($RouteDiagnostics.RemoteAddress)" -Status "RouteDiagnostics" -CurrentOperation "Finding route" -SecondsRemaining -1 -PercentComplete -1

            try
            {
                $RouteDiagnostics.SelectedSourceAddress, $RouteDiagnostics.SelectedNetRoute = `
                    Find-NetRoute -RemoteIPAddress $RouteDiagnostics.RemoteAddress -LocalIPAddress $RouteDiagnostics.ConstrainSourceAddress -InterfaceIndex $RouteDiagnostics.ConstrainInterfaceIndex -ErrorAction SilentlyContinue

                $RouteDiagnostics.OutgoingNetAdapter = $RouteDiagnostics.SelectedNetRoute | Get-NetAdapter -IncludeHidden -ErrorAction SilentlyContinue

                $RouteDiagnostics.OutgoingInterfaceAlias = $RouteDiagnostics.SelectedNetRoute.InterfaceAlias
                $RouteDiagnostics.OutgoingInterfaceIndex = $RouteDiagnostics.SelectedNetRoute.InterfaceIndex
                $RouteDiagnostics.OutgoingInterfaceDescription =  $RouteDiagnostics.OutgoingNetAdapter.InterfaceDescription
            }
            catch
            {
                Write-Debug "Error occured while finding route information. Exception: $($_.Exception.Message)"
                netsh trace stop sessionname=tnc | out-null
                return
            }

            if ($RouteDiagnostics.Detailed)
            {
                Write-Progress  -Activity "Test-NetConnection :: $($RouteDiagnostics.RemoteAddress)" -Status "RouteDiagnostics" -CurrentOperation "Parsing Route Events" -SecondsRemaining -1 -PercentComplete -1

                $TraceResults += netsh trace stop sessionname=tnc

                if (-not (Test-Path -Path $LogFile))
                {
                    $Message = "Error occured while collection routing events. Error: " + $TraceResults
                    Write-Warning $Message
                    return
                }

                ##Search for all relevant routing events: IpSourceAddressSelection (ID 1326), IpSortedAddressPairs (ID 1327), RouteLookup (ID 1370), IpRouteSelection (ID 1383), and IpRouteBlocked (ID 1384)
                $AllRoutingEvents = Get-WinEvent -Oldest -FilterHashtable @{ Path=$LogFile; ProviderName='Microsoft-Windows-TCPIP'; ID=@(1326, 1327, 1370, 1383, 1384) } | Where-Object { $_.Message -ne $null }
                if ($AllRoutingEvents.Count -eq 0)
                {
                    ##There may be no route or address selection events if there was only one matching route, but there should've been at least one route lookup event.
                    Write-Warning "No TCPIP routing events collected from $LogFile. The trace might have failed."
                }
                else
                {
                    ##Get route selection events: [IpRouteSelection (ID 1383) and IpRouteBlocked (ID 1384)] containing the remote IP
                    $RouteEvents = $AllRoutingEvents | Where-Object { (($_.Id -eq 1383) -or ($_.Id -eq 1384)) -and $_.Message.Contains("$($RouteDiagnostics.RemoteAddress) ") }
                    foreach ($event in $RouteEvents)
                    {
                        if ($RouteDiagnostics.RouteSelectionEvents -notcontains $($event.Message))
                        {
                            $RouteDiagnostics.RouteSelectionEvents += "$($event.Message)"
                        }
                    }

                    ##Get source address selection events: [IpSourceAddressSelection (ID 1326)] containing the remote IP
                    $SrcAddrEvents = $AllRoutingEvents | Where-Object { ($_.Id -eq 1326) -and $_.Message.Contains("$($RouteDiagnostics.RemoteAddress) ") }
                    foreach ($event in $SrcAddrEvents)
                    {
                        if ($RouteDiagnostics.SourceAddressSelectionEvents -notcontains $($event.Message))
                        {
                            $RouteDiagnostics.SourceAddressSelectionEvents += "$($event.Message)"
                        }
                    }

                    ##Get destination address selection events: [IpSortedAddressPairs (ID 1327)] containing the resolved IPs
                    $ResolvedAddrs = $RouteDiagnostics.ResolvedAddresses | ForEach-Object { $_.IPAddressToString }
                    $DstAddrEvents = $AllRoutingEvents | Where-Object { ($_.Id -eq 1327) -and (($CurrMessage = $_.Message) -ne $null) -and (($ResolvedAddrs | ForEach-Object { $CurrMessage.Contains("$_)") }) -contains $true) }
                    foreach ($event in $DstAddrEvents)
                    {
                        if ($RouteDiagnostics.DestinationAddressSelectionEvents -notcontains $($event.Message))
                        {
                            $RouteDiagnostics.DestinationAddressSelectionEvents += "$($event.Message)"
                        }
                    }
                }

                $RouteDiagnostics.LogFile = $LogFile
            }

            $RouteDiagnostics.RouteDiagnosticsSucceeded = $True

            return
        }
    }

    Process
    {
        switch ($PSCmdlet.ParameterSetName)
        {

        ##Route diagnostics parameterset
        "NetRouteDiagnostics"
        {
            $Return = [NetRouteDiagnostics]::new()
            $Return.ComputerName = $ComputerName

            if ($ConstrainSourceAddress -ne "")
            {
                $Return.ConstrainSourceAddress = $ConstrainSourceAddress
            }

            $Return.ConstrainInterfaceIndex = $ConstrainInterface

            $Return.Detailed = ($InformationLevel -eq "Detailed")
            if ($Return.Detailed -and (-not (CheckIfAdmin))) {
                Write-Warning "'-InformationLevel Detailed' requires elevation (Run as administrator)."
                $Return.Detailed = $False
            }

            DiagnoseRouteSelection -RouteDiagnostics $Return

            return $Return
        }

        ##Test connection parametersets
        default
        {
            ##Construct the return object and fill basic details
            $Return = [TestNetConnectionResult]::new()
            $Return.ComputerName = $ComputerName
            $Return.Detailed = ($InformationLevel -eq "Detailed")

            #### Begin Name Resolution ####

            $Return.ResolvedAddresses = ResolveTargetName -TargetName $ComputerName
            if ($Return.ResolvedAddresses -eq $null)
            {
                if ($InformationLevel -eq "Quiet")
                {
                    return $False
                }

                $Return.NameResolutionSucceeded = $False
                return $Return
            }

            $Return.RemoteAddress = $Return.ResolvedAddresses[0]
            $Return.NameResolutionSucceeded = $True
            #### End of Name Resolution ####

            #### Begin TCP test ####

            ##Attempt TCP test only if Port or CommonTCPPort is specified
            $AttemptTcpTest = ($PSCmdlet.ParameterSetName -eq "CommonTCPPort") -or ($PSCmdlet.ParameterSetName -eq "RemotePort")
            if ($AttemptTcpTest)
            {
                $Return.TcpTestSucceeded = $False

                switch ($CommonTCPPort)
                {
                ""      {$Return.RemotePort = $Port}
                "HTTP"  {$Return.RemotePort = 80}
                "RDP"   {$Return.RemotePort = 3389}
                "SMB"   {$Return.RemotePort = 445}
                "WINRM" {$Return.RemotePort = 5985}
                }

                ##Try TCP connect using all resolved addresses until it succeeds
                $Iter = 0
                while (($Iter -lt $Return.ResolvedAddresses.Count) -and (-not $Return.TcpTestSucceeded))
                {
                    $Return.TcpTestSucceeded = TestTCP -TargetIPAddress $Return.ResolvedAddresses[$Iter] -TargetPort $Return.RemotePort
                    ##Output a warning message if the TCP test didn't succeed
                    if (-not $Return.TcpTestSucceeded)
                    {
                        Write-Warning "TCP connect to ($($Return.ResolvedAddresses[$Iter]) : $($Return.RemotePort)) failed"
                    }
                    $Iter++
                }

                if ($Return.TcpTestSucceeded)
                {
                    ##Get the remote address that was actually used for connection
                    $Return.RemoteAddress = $Return.ResolvedAddresses[$Iter - 1]
                }

                ##If the user specified "quiet" then we should only return a boolean
                if ($InformationLevel -eq "Quiet")
                {
                    return $Return.TcpTestSucceeded
                }
            }
            #### End of TCP test ####

            #### Begin Ping test ####

            ##Attempt Ping test only if TCP test is not attempted or TCP test failed
            $AttemptPingTest = (-not $AttemptTcpTest) -or (-not $Return.TcpTestSucceeded)
            if ($AttemptPingTest)
            {
                $Return.PingSucceeded = $False

                ##Try Ping using all resolved addresses until it succeeds
                $Iter = 0
                while (($Iter -lt $Return.ResolvedAddresses.Count) -and (-not $Return.PingSucceeded))
                {
                    $Return.PingReplyDetails = PingTest -TargetIPAddress $Return.ResolvedAddresses[$Iter]
                    if ($Return.PingReplyDetails -ne $null)
                    {
                        $Return.PingSucceeded = ($Return.PingReplyDetails.Status -eq [System.Net.NetworkInformation.IPStatus]::Success)
                    }

                    ##Output a warning message if Ping didn't succeed
                    if (-not $Return.PingSucceeded)
                    {
                        $WarningString = "Ping to $($Return.ResolvedAddresses[$Iter]) failed"
                        if ($Return.PingReplyDetails -ne $null)
                        {
                            $WarningString += " with status: $($Return.PingReplyDetails.Status)"
                        }
                        Write-Warning $WarningString
                    }
                    $Iter++
                }

                if ($Return.PingSucceeded)
                {
                    ##Get the remote address that was actually used for Ping
                    $Return.RemoteAddress = $Return.ResolvedAddresses[$Iter - 1]
                }

                ##If the user specified "quiet" then we should only return a boolean
                if ($InformationLevel -eq "Quiet")
                {
                    return $Return.PingSucceeded
                }
            }
            #### End of Ping test ####

            #### Begin TraceRoute ####

            ##TraceRoute, only occurs if switched by the user
            if ($TraceRoute -eq $True)
            {
                $Return.TraceRoute = TraceRoute -TargetIPAddress $Return.RemoteAddress -Hops $Hops
            }
            #### End of TraceRoute ####

            $Return = ResolveDNSDetails -TestNetConnectionResult $Return
            $Return = ResolveNetworkSecurityDetails -TestNetConnectionResult $Return
            $Return = ResolveRoutingandAdapterWMIObjects -TestNetConnectionResult $Return

            return $Return
        }
        }
    }
}

##Export cmdlet and alias to module
New-Alias TNC Test-NetConnection
Export-ModuleMember -Alias TNC -Function Test-NetConnection
