Test-NetFlow.psm1
Add-Type -ReferencedAssemblies ([Microsoft.Management.Infrastructure.Ciminstance].Assembly.Location) -TypeDefinition @'
public class TestNetFlowResult { public string ComputerName = null; //The Remote IP address used for connectivity public System.Net.IPAddress RemoteAddress = null; //Indicates if the Ping was successful public bool PingSucceeded; //Details of the ping public System.Net.NetworkInformation.PingReply PingReplyDetails = null; //The TCP socket public System.Net.Sockets.Socket TcpClientSocket = null; //If the test succeeded public bool TcpTestSucceeded; //Remote port used public uint RemotePort; //The results of the traceroute public string[] TraceRoute = null; //An indicator to the formatter that details should be shown public bool Detailed; //Information on the interface used for connectivity public string InterfaceAlias = null; public uint InterfaceIndex; public string InterfaceDescription = null; public Microsoft.Management.Infrastructure.CimInstance NetAdapter = null; public Microsoft.Management.Infrastructure.CimInstance NetRoute = null; //Source IP address public Microsoft.Management.Infrastructure.CimInstance SourceAddress = null; //DNS information public bool NameResolutionSucceeded; public object BasicNameResolution = null; public object LLMNRNetbiosRecords = null; public object DNSOnlyRecords = null; public object AllNameResolutionResults = null; //NetSec Info public bool IsAdmin; //If the test succeeded public string NetworkIsolationContext = null; public Microsoft.Management.Infrastructure.CimInstance[] MatchingIPsecRules = null; //Message Content and Resposne public string Transport; public string Message; public string Response; } '@ function Test-NetFlow { [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")][Parameter(ParameterSetName = "UDP", Mandatory = $True, ValueFromPipelineByPropertyName = $true)] [Alias('RemotePort')] [ValidateRange(1,65535)] [int]$Port = 0, [Parameter(ParameterSetName = "CommonTCPPort")] [Parameter(ParameterSetName = "RemotePort")] [Parameter(ParameterSetName = "UDP")] [String]$Message ="", [Parameter(ParameterSetName = "UDP", Mandatory = $True)] [Switch]$UDP , [Parameter()] [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 = new-object System.Security.Principal.WindowsPrincipal($CurrentIdentity) $AdminPrincipal = [System.Security.Principal.WindowsBuiltInRole]::Administrator return $CurrentSecurityPrincipal.IsInRole($AdminPrincipal) } ##Description: Returns the remote IP address used for connectivity. ##Input: The user-provided computername that will be pinged/tested ##Output: An IP address for the remote host function ResolveTargetName { param($TargetName,$TargetPort) $RemoteAddress = [System.Net.IPAddress]::Loopback if ([System.Net.IPAddress]::TryParse($TargetName, [ref]$RemoteAddress)) { return $RemoteAddress } try { $UDPClient = new-object System.Net.Sockets.UDPClient($TargetName,$TargetPort) $UDPClient.Connect($TargetName,$TargetPort) return $UDPClient.Client.RemoteEndPoint.Address } catch [System.Net.Sockets.SocketException] { $Message = "Name resolution of $TargetName failed -- Status: " + $_.Exception.InnerException.SocketErrorCode.ToString() Write-Warning $Message return $null } } ##Description: Pings a specified host ##Input: IP address to ping ##Output: PingReplyDetails for the ping attempt to host function PingTest { param($TargetIPAddress) $Ping = new-object System.Net.NetworkInformation.Ping ##Indeterminate progress indication Write-Progress -Activity "Test-NetConnection :: $TargetIPAddress" -Status "Ping/ICMP Test" -CurrentOperation "Waiting for echo reply" -SecondsRemaining -1 -PercentComplete -1 try { return $Ping.Send($TargetIPAddress) } catch [System.Net.NetworkInformation.PingException] { $Message = "Ping to $TargetIPAddress failed -- Status: " + $_.Exception.InnerException.Message.ToString() Write-Warning $Message return $null } } ##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 = new-object System.Net.NetworkInformation.Ping $PingOptions = new-object System.Net.NetworkInformation.PingOptions $PingOptions.Ttl=1 $DataBuffer=new-object byte[] 10 $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.Send($TargetIPAddress, 4000, $DataBuffer, $PingOptions) if($PingReplyDetails.Address -eq $null) { $ReturnTrace += $PingReplyDetails.Status.ToString() } else { $ReturnTrace += $PingReplyDetails.Address.IPAddressToString } } catch { Write-Debug "Exception thrown in PING send" $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 } return $ReturnTrace } ##Description: Sends a UDP datagram and waits 4 seconds for a response. ##Input: IP address and port to connect to ##Output: If the connection succeeded (as a boolean), and the socket, and then the data function TestUDP { param($TargetName,$TargetPort) try{ $UDPclient = $null; $ProgressString = "Test-NetConnection - " + $TargetName + ":" + $TargetPort Write-Progress -Activity $ProgressString -Status "Preparing to send UDP datagram" -CurrentOperation "Binding" -SecondsRemaining -1 -PercentComplete -1 $UDPclient = new-object System.Net.Sockets.UDPclient($TargetName, $TargetPort); Write-Debug $TargetName Write-Debug $TargetPort $UDPclient.Client.ReceiveTimeout = 3000; $Encoding = new-object System.Text.ASCIIEncoding; $encodedTransmission = $Encoding.GetBytes($Message); $void = [Array]::Resize([ref] $encodedTransmission,500); $void = $UDPclient.Send($encodedTransmission,500); Write-Debug "Sending UDP echo request." Start-Sleep -Seconds 1 } catch [Exception] { Write-Debug "Exception thrown in UDP bind/sends;" Write-Debug $_.Exception.Message; throw $_.Exception; return $null } try{ $Response = ""; $receivedTransmission = new-object byte[] 500; $remoteendpoint = new-object System.Net.IPEndpoint([System.Net.IPAddress]::Any,0) $receivedTransmission = $UDPclient.Receive([ref] $remoteendpoint); $Response = $Encoding.GetString($receivedTransmission); $Response = $Encoding.GetString($receivedTransmission).Replace("`0",""); return $Response; } catch [Exception] { Write-Debug "Exception thrown in UDP receive;" Write-Debug $_.Exception.Message; throw $_.Exception; return $null } } ##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), and the socket, and then the data function TestTCP { param($TargetName,$TargetPort) try { $ProgressString = "Test-NetConnection - " + $TargetName + ":" + $TargetPort Write-Progress -Activity $ProgressString -Status "Attempting TCP connect" -CurrentOperation "Waiting for response" -SecondsRemaining -1 -PercentComplete -1 $TCPClient = new-object System.Net.Sockets.TcpClient($TargetName, $TargetPort); $Response = ""; if($Message -ne "") { $testStream = $TCPClient.GetStream(); $Encoding = new-object System.Text.ASCIIEncoding; $encodedTransmission = $Encoding.GetBytes($Message); $void = [Array]::Resize([ref] $encodedTransmission,500); $testStream.Write($encodedTransmission,0,500); $receivedTransmission = new-object byte[] 500; $void = $testStream.Read($receivedTransmission,0,500); $Response = $Encoding.GetString($receivedTransmission).Replace("`0",""); Write-Debug "Response: $Response" } return $TCPClient.Client, $TCPClient.Connected, $Response; } catch [Exception] { Write-Debug "Exception thrown in TCP connect" Write-Debug $_.Exception.Message return $null } } ##Description: Modifies the provided object with the correct local connectivty information ##Input: TestNetFlowResults object that will be modified ##Output: Modified TestNetFlowResult object function ResolveRoutingandAdapterWMIObjects { param($TestNetFlowResult) try { $TestNetFlowResult.SourceAddress, $TestNetFlowResult.NetRoute = Find-NetRoute -RemoteIPAddress $TestNetFlowResult.RemoteAddress -ErrorAction SilentlyContinue $TestNetFlowResult.NetAdapter = $TestNetFlowResult.NetRoute | Get-NetAdapter -IncludeHidden -ErrorAction SilentlyContinue $TestNetFlowResult.InterfaceAlias = $TestNetFlowResult.NetRoute.InterfaceAlias $TestNetFlowResult.InterfaceIndex = $TestNetFlowResult.NetRoute.InterfaceIndex $TestNetFlowResult.InterfaceDescription = $TestNetFlowResult.NetAdapter.InterfaceDescription } catch { Write-Debug "Exception thrown in ResolveRoutingandAdapterWMIObjects" } return $TestNetFlowResult } ##Description: Resolves the DNS details for the computername ##Input: The TestNetFlowResults object that will be "filled in" with DNS information ##Output: The modified TestNetFlowResults object function ResolveDNSDetails { param($TestNetFlowResult) $TestNetFlowResult.DNSOnlyRecords = @( Resolve-DnsName $ComputerName -DnsOnly -NoHostsFile -Type A_AAAA -ErrorAction SilentlyContinue | where-object {($_.QueryType -eq "A") -or ($_.QueryType -eq "AAAA") } ) $TestNetFlowResult.LLMNRNetbiosRecords = @( Resolve-DnsName $ComputerName -LlmnrNetbiosOnly -NoHostsFile -ErrorAction SilentlyContinue | where-object {($_.QueryType -eq "A") -or ($_.QueryType -eq "AAAA") } ) $TestNetFlowResult.BasicNameResolution = @(Resolve-DnsName $ComputerName -ErrorAction SilentlyContinue | where-object {($_.QueryType -eq "A") -or ($_.QueryType -eq "AAAA")} ) $TestNetFlowResult.AllNameResolutionResults = $Return.BasicNameResolution + $Return.DNSOnlyRecords + $Return.LLMNRNetbiosRecords | Sort-Object -Unique -Property Address return $TestNetFlowResult } ##Description: Resolves the network security details for the computername ##Input: The TestNetFlowResults object that will be "filled in" with network security information ##Output: Teh modified TestNetFlowResults object function ResolveNetworkSecurityDetails { param($TestNetFlowResult) $TestNetFlowResult.IsAdmin = CheckIfAdmin $NetworkIsolationInfo = Invoke-WmiMethod -Namespace root\standardcimv2 -Class MSFT_NetAddressFilter -Name QueryIsolationType -ArgumentList $TestNetFlowResult.InterfaceIndex,$TestNetFlowResult.RemoteAddress -ErrorAction SilentlyContinue switch ($NetworkIsolationInfo.IsolationType) { 1 {$TestNetFlowResult.NetworkIsolationContext = "Private Network";} 0 {$TestNetFlowResult.NetworkIsolationContext = "Loopback";} 2 {$TestNetFlowResult.NetworkIsolationContext = "Internet";} } ##Elevation is required to read IPsec information for the connection. if($TestNetFlowResult.IsAdmin) { $TestNetFlowResult.MatchingIPsecRules = Find-NetIPsecRule -RemoteAddress $TestNetFlowResult.RemoteAddress -RemotePort $TestNetFlowResult.RemotePort -Protocol TCP -ErrorAction SilentlyContinue } return $TestNetFlowResult } } Process { ##Construct the return object and fill basic details $Return = new-object TestNetFlowResult $Return.ComputerName = $ComputerName $Return.Detailed = ($InformationLevel -eq "Detailed") $Return.Transport = "TCP" if($UDP) { if($Message -eq "") { Write-Debug "No Message Specified for UDP"; $Message = "UDP Echo"; } $Return.Transport = "UDP"; } $Return.Message = $Message #UDP connect is done, to simplify name resolution and source address detection $Return.RemoteAddress = ResolveTargetName -TargetName $ComputerName -TargetPort $Return.RemotePort if($Return.RemoteAddress -eq $null) { if($InformationLevel -eq "Quiet") { return $false } return $Return } else { $Return.NameResolutionSucceeded = $True } ##Ping $Return.PingReplyDetails = PingTest -TargetIPAddress $Return.RemoteAddress if ($Return.PingReplyDetails -ne $null) { $Return.PingSucceeded = ($Return.PingReplyDetails.Status -eq [System.Net.NetworkInformation.IPStatus]::Success) ##Output a warning message if the ping did not succeed if(!$Return.PingSucceeded) { $WarningString = "Ping to $ComputerName failed -- Status: " + $Return.PingReplyDetails.Status.ToString() write-warning $WarningString } } #### Begin TCP test #### $TCPTestAttempted = $False ##Check if the user specified a port directly if ($Port -ne 0) { Write-Debug "User specified a port directly." $Return.RemotePort = $Port; if($UDP) { $Return.Response = TestUDP -TargetName $Return.ComputerName -TargetPort $Return.RemotePort } else { $TCPTestAttempted = $True $Return.TcpClientSocket, $Return.TcpTestSucceeded, $Return.Response = TestTCP -TargetName $Return.ComputerName -TargetPort $Return.RemotePort } } ##If no port was specified directly, then we check to see if a CommonTCPPort was specified if ($CommonTCPPort -ne "") { switch ($CommonTCPPort) { "HTTP" {$Return.RemotePort = 80} "RDP" {$Return.RemotePort = 3389} "SMB" {$Return.RemotePort = 445} "WINRM" {$Return.RemotePort = 5985} } if($UDP) { $Return.Response = TestUDP -TargetName $Return.ComputerName -TargetPort $Return.RemotePort } else { $TCPTestAttempted = $True $Return.TcpClientSocket, $Return.TcpTestSucceeded, $Return.Response = TestTCP -TargetName $Return.ComputerName -TargetPort $Return.RemotePort } } ##If the user specified "quiet" then we should only return a boolean if($InformationLevel -eq "Quiet") { if($TCPTestAttempted) { return $Return.TcpTestSucceeded } else { return $Return.PingSucceeded } } ##If we did a TCP test and it failed (did not succeed) then we need to write a warning if((!$Return.TcpTestSucceeded) -and ($TCPTestAttempted)) { $WarningString = "TCP connect to $ComputerName"+ ":" + $Return.RemotePort +" failed" write-warning $WarningString } #### End of TCP test #### ##TraceRoute, only occurs if switched by the user if($TraceRoute -eq $True) { $Return.TraceRoute = TraceRoute -TargetIPAddress $Return.RemoteAddress -Hops $Hops } $Return = ResolveDNSDetails -TestNetFlowResult $Return $Return = ResolveNetworkSecurityDetails -TestNetFlowResult $Return $Return = ResolveRoutingandAdapterWMIObjects -TestNetFlowResult $Return return $Return } } ##Export cmdlet and alias to module New-Alias TNC Test-NetFlow Export-ModuleMember -Alias TNC -Function Test-NetFlow |