Find-NetworkDevice.ps1
function Invoke-ForeachParallel { param ( [ScriptBlock] $Process, [Int] $ThrottleLimit = 32, [Int] $CheckIntervalMilliseconds = 200, [Int] $TimeoutSec = -1, [Switch] $UseLocalVariables ) Begin { function CheckForCompleted { Foreach($thread in $ThreadList) { If ($thread.Runspace.isCompleted) { if($thread.powershell.Streams.Error.Count -gt 0) { foreach($ErrorRecord in $thread.powershell.Streams.Error) { Write-Error -ErrorRecord $ErrorRecord } } if ($thread.TimedOut -ne $true) { $thread.powershell.EndInvoke($thread.Runspace) } $thread.Done = $true } elseif ($TimeoutSec -gt 0 -and $thread.TimedOut -ne $true) { $runtimeSeconds = ((Get-Date) - $thread.StartTime).TotalSeconds if ($runtimeSeconds -gt $TimeoutSec) { Write-Error -Message "Thread $($thread.ThreadID) timed out." $thread.TimedOut = $true $null = $thread.PowerShell.BeginStop({}, $null) } } } $ThreadCompletedList = $ThreadList | Where-Object { $_.Done -eq $true } if ($ThreadCompletedList.Count -gt 0) { foreach($threadCompleted in $ThreadCompletedList) { $threadCompleted.powershell.Stop() $threadCompleted.powershell.dispose() $threadCompleted.Runspace = $null $threadCompleted.powershell = $null $ThreadList.remove($threadCompleted) } Start-Sleep -milliseconds $CheckIntervalMilliseconds } } $SessionState = [initialsessionstate]::CreateDefault() if ($UseLocalVariables) { $ps = [Powershell]::Create() $null = $ps.AddCommand('Get-Variable') $oldVars = $ps.Invoke().Name $ps.Runspace.Close() $ps.Dispose() Get-Variable | Where-Object { $_.Name -notin $oldVars } | Foreach-Object { $SessionState.Variables.Add((New-Object System.Management.Automation.Runspaces.SessionStateVariableEntry($_.Name, $_.Value, $null))) } } $RunspacePool = [Runspacefactory]::CreateRunspacePool(1, $ThrottleLimit, $SessionState, $host) $RunspacePool.Open() $ThreadList = New-Object System.Collections.ArrayList } Process { $Code = '$args | Foreach-Object { ' + $Process.toString() + '}' $powershell = [powershell]::Create() $null = $PowerShell.AddScript($Code).AddArgument($_) $powershell.RunspacePool = $RunspacePool $threadID++ $threadInfo = @{ PowerShell = $powershell StartTime = Get-Date ThreadID = $threadID Runspace = $powershell.BeginInvoke() } $null = $ThreadList.Add($threadInfo) Write-Output (CheckForCompleted) } End { try { Do { Write-Output (CheckForCompleted) } while ($ThreadList.Count -gt 0) } finally { foreach($thread in $ThreadList) { $thread.powershell.dispose() $thread.Runspace = $null $thread.powershell = $null } $RunspacePool.close() $RunspacePool.Dispose() [GC]::Collect() } } } function Test-PingRange { [CmdletBinding()] param ( [Switch] $SuccessOnly ) Get-MyIpAddress | Get-IpRange | Invoke-ForeachParallel { Test-Ping -ComputerName $_ -TimeoutMillisec 3000 } -ThrottleLimit 64 | Where-Object { $_.Response -or (!$SuccessOnly) } } function Test-PortRange { param ( [Parameter(Mandatory)] [string] $HostName, [Parameter(Mandatory)] [uint32[]] $Port, [Switch] $SuccessOnly ) $Port | Invoke-ForeachParallel { Test-Port -ComputerName $HostName -Port $_ -TimeoutMillisec 3000 } -ThrottleLimit 64 -UseLocalVariables | Where-Object { $_.Response -or (!$SuccessOnly) } } function Test-Port { <# .SYNOPSIS Tests Port of a network computer .EXAMPLE Test-Port -ComputerName 192.168.2.144 -Port 80 checks to see if computer at 192.168.2.144 listens to port 80 (and thus is a webserver) #> param ( [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)] [string] $ComputerName, [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)] [int] $Port, [int] $TimeoutMilliSec = 1000 ) process { try { $client = [System.Net.Sockets.TcpClient]::new() $task = $client.ConnectAsync($ComputerName, $Port) if ($task.Wait($TimeoutMilliSec)) { $success = $client.Connected } else { $success = $false } } catch { $success = $false } finally { $ip = $client.Client.RemoteEndPoint.Address.IPAddressToString $client.Close() $client.Dispose() } [PSCustomObject]@{ HostName = $ComputerName IP = $ip Port = $Port Response = $success } } } function Test-Ping { <# .SYNOPSIS Sends ICMP request and waits for a maximum of -TimeoutMillisec milliseconds for an answer .EXAMPLE Test-Ping -ComputerName microsoft.com pings microsoft.com .EXAMPLE '127.0.0.1','google.de','99.99.99.99' | Test-Ping -TimeoutMillisec 2000 pings three uris and waits a maximum of 3 seconds for an answer #> param ( [Parameter(Mandatory,ValueFromPipeline)] [string] $ComputerName, [int] $TimeoutMillisec = 1000 ) begin { $obj = [System.Net.NetworkInformation.Ping]::new() } process { try { $obj.Send($ComputerName, $TimeoutMillisec) | Select-Object -Property @{N='HostName';E={$ComputerName}},@{N='IP';E={$_.Address}}, @{N='Port';E={'ICMP'}}, @{N='Response';E={$_.Status -eq 'Success'}} } catch [System.Net.NetworkInformation.PingException] { Write-Warning "$Computername ist kein g�ltiger Computername" } } end { $obj.Dispose() } } function Get-MyIpAddress { <# .SYNOPSIS Gets the IPv4 addresses of all active network adapters .EXAMPLE Get-MyIpAddress gets the ip addresses of all network adapters that are up .EXAMPLE Get-MyIpAddress | Get-IpRange get all ip addresses in the current segment #> Get-NetAdapter | Where-Object Status -eq 'Up' | Get-NetIPAddress -AddressFamily IPv4 -PrefixOrigin Dhcp,Manual } function Get-IpRange { <# .SYNOPSIS Gets list of ip addresses .EXAMPLE Get-IpRange -From 192.168.2.1 -To 192.168.4.22 gets all ip addresses in the specified range .EXAMPLE Get-MyIpAddress | Get-IpRange get all ip addresses in the current segment #> [CmdletBinding(DefaultParameterSetName='Manual')] param ( [Parameter(Mandatory,ParameterSetName='Manual',Position=0)] [ipaddress] $From, [Parameter(Mandatory,ParameterSetName='Manual',Position=1)] [ipaddress] $To, [Parameter(Mandatory,ParameterSetName='Auto',Position=0,ValueFromPipeline)] [PSTypeName('Microsoft.Management.Infrastructure.CimInstance#root/standardcimv2/MSFT_NetIPAddress')] [CimInstance] $NetIpAddress, [Hashtable] $Log = @{} ) begin { function ConvertTo-UInt32 { param ([string]$ipAddress) $bytes = [ipaddress]::Parse($ipAddress).GetAddressBytes() [Array]::Reverse($bytes) return [BitConverter]::ToUInt32($bytes, 0) } function ConvertTo-IPAddress { param ([UInt32]$ipInt) $bytes = [BitConverter]::GetBytes($ipInt) [Array]::Reverse($bytes) return [ipaddress]::new($bytes) } $rangeId = 0 } process { $rangeId++ if ($PSCmdlet.ParameterSetName -eq 'Auto') { $ipAddress = $NetIpAddress.IPAddress $prefixLength = $NetIpAddress.PrefixLength $ipInt = ConvertTo-UInt32 -ipAddress $ipAddress $subnetInt = [uint32]([math]::Pow(2, 32) - [math]::Pow(2, 32 - $prefixLength)) $networkInt = $ipInt -band $subnetInt $broadcastInt = $networkInt -bor -bnot $subnetInt # Calculate start and end IP addresses $startIpInt = $networkInt + 2 $endIpInt = $broadcastInt - 1 $from = ConvertTo-IPAddress -ipInt $startIpInt $to = ConvertTo-IPAddress -ipInt $endIpInt } $key = "Range$rangeid" $value = '{0}-{1}' -f $from.IPAddressToString, $to.IPAddressToString $log[$key] = $value $ipFromBytes =$From.GetAddressBytes() $ipToBytes = $to.GetAddressBytes() # change endianness (reverse bytes) [array]::Reverse($ipFromBytes) [array]::Reverse($ipToBytes) # convert reversed bytes to uint32 $start=[BitConverter]::ToUInt32($ipFromBytes, 0) $end=[BitConverter]::ToUInt32($ipToBytes, 0) # enumerate from start to end uint32 for($x = $start; $x -le $end; $x++) { # split uit32 back into bytes $ip=[bitconverter]::getbytes($x) # reverse bytes back to normal [Array]::Reverse($ip) # output ipv4 address as string $ip -join '.' } } } function Find-NetworkDevice { <# .SYNOPSIS Finds the IPv4 ip address of a device in your network .DESCRIPTION Interactive tool to find IPv4 address of devices in your network. When you run this command, you are instructed to first turn off the device you want to find. Once you turned it off and press ENTER, the tool scans your network(s) for any responding device. Next, you are instructed to turn on the device, then press ENTER. Now, the tool again scans the network(s). Once done, it compares both results and lists the differences. You will be presented with the IPv4 addresses that weren't responding on first scan but show up once the device was turned on. Note that results may not always be accurate due to network issues. If in doubt, run the tool again. .EXAMPLE Find-NetworkDevice identifies new devices that respond to ICMP ping requests .EXAMPLE Find-NetworkDevice -WebInterface identifies new devices that expose a http: webinterface on port 80 .EXAMPLE Find-NetworkDevice -OpenInBrowser identifies new devices that expose a http: webinterface on port 80, and opens the found web interfaces in your browser Do not be surprised when a number of web interfaces are opened. You might be surprised to discover how many home automation devices and entertainment systems in your home expose a secret web interface. .EXAMPLE Find-NetworkDevice -FindAllWebInterfaces identifies all devices currently active in your network that expose a http: webinterface on port 80, and opens the found web interfaces in your browser This command will perform just one scan and finds all active devices. Do not be surprised when a number of web interfaces are opened. You might be surprised to discover how many home automation devices and entertainment systems in your home expose a secret web interface. .EXAMPLE Find-NetworkDevice -FindAllWebInterfaces -ThrottleLimit 64 -Timeout 2000 -Verbose identifies all devices currently active in your network that expose a http: webinterface on port 80, and opens the found web interfaces in your browser This command experiments with the optimization parameters and outputs scan metrics to see whether (and how much) the scan speed was improved, and whether there was an impact on the number of found devices. #> [CmdletBinding()] param ( # scans for new devices that respond to port 80 [Switch] $WebInterface, # scans for new devices that respond to port 80 and opens the found web interfaces in your browser [Switch] $OpenInBrowser, # scans for all currently active devices that respond to port 80 and opens the found web interfaces in your browser [Switch] $FindAllWebInterfaces, # throttlelimit defines the number of parallel processes # the higher this value, the faster will the scan be (as more ip addresses are examined in parallel) # when you increase this value too much, your network adapter will run out of resources, and your results are not complete $ThrottleLimit = 64, # timeout to wait for device response in milliseconds # the higher this value, the longer will a scan take # when you decrease this value too much, slow devices or devices that need to initialize port responses may not be discovered # ESPEasy devices i.e. require a long timeout of >=4000ms $Timeout = 4000 ) # port number to use for identifying internal web servers # typically, devices use port 80 for default http web interfaces # if you reconfigured your devices to use a different port, adjust the port: $port = 80 $doPortScan = $WebInterface -or $OpenInBrowser -or $FindAllWebInterfaces Write-Progress -Activity "Initialization" -Status "Calculating IP Addresses In Current Network Range." $me = Get-MyIpAddress $log = @{} $range = $me | Get-IpRange -Log $log [string[]]$scan1 = $null [System.Diagnostics.Stopwatch]$stopwatch = [System.Diagnostics.Stopwatch]::new() if (!$FindAllWebInterfaces) { Write-Host "[1] Make sure the device is currently NOT TURNED ON. TURN IT OFF if necessary." -ForegroundColor Yellow Write-Host "Press ENTER when you are ready." -ForegroundColor Green $null = Read-Host Write-Progress -Activity "Baseline Scanning Network (Your IP is $($me.IPAddress))" -Status "Scanning $($log.Values -join ',')" $stopwatch.Start() $scan1 = if ($doPortScan) { $range | Invoke-ForeachParallel { Test-Port -ComputerName $_ -TimeoutMillisec $Timeout -Port $port } -ThrottleLimit $ThrottleLimit -UseLocalVariables | Where-Object Response | Select-Object -ExpandProperty HostName } else { $range | Invoke-ForeachParallel { Test-Ping -ComputerName $_ -TimeoutMillisec $Timeout } -ThrottleLimit $ThrottleLimit -UseLocalVariables | Where-Object Response | Select-Object -ExpandProperty Ip } # output metrics when user specified -Verbose $stopwatch.Stop() $message = 'Initial baseline scan took {0:n1} seconds.' -f $stopwatch.Elapsed.TotalSeconds Write-Verbose $message Write-Host "[2] NOW TURN ON the device." -ForegroundColor Yellow Write-Host "Press ENTER when you are ready." -ForegroundColor Green $null = Read-Host } [string[]]$scan2 = if ($doPortScan) { if (!$FindAllWebInterfaces) { # wait 10 seconds to make sure the device is responsive Write-Progress -Activity 'Waiting' -Status "Waiting 10sec for webinterface to become responsive..." Start-Sleep -Seconds 10 } Write-Progress -Activity "Discovery..." -Status 'Searching for devices...' $stopwatch.Start() $range | Where-Object { $FindAllWebInterfaces -or ($_ -notin $scan1) } | Invoke-ForeachParallel { Test-Port -ComputerName $_ -TimeoutMillisec $Timeout -Port $port } -ThrottleLimit $ThrottleLimit -UseLocalVariables | Where-Object Response | Select-Object -ExpandProperty HostName } else { # wait 3 seconds to make sure the device is responsive Write-Progress -Activity 'Waiting' -Status "Waiting 3sec for device to become responsive..." Start-Sleep -Seconds 3 Write-Progress -Activity "Discovery..." -Status 'Searching for new devices...' $stopwatch.Start() $range | Where-Object { $_ -notin $scan1 } | Invoke-ForeachParallel { Test-Ping -ComputerName $_ -TimeoutMillisec $Timeout } -ThrottleLimit $ThrottleLimit -UseLocalVariables | Where-Object Response | Select-Object -ExpandProperty Ip } $result = $scan2 | Where-Object { $FindAllWebInterfaces -or ($_ -notin $scan1) } # output metrics when user specified -Verbose $stopwatch.Stop() $message = 'Total scan time was {0:n1} seconds. {1} devices found.' -f $stopwatch.Elapsed.TotalSeconds, $result.Count Write-Verbose $message | Out-Default if ($OpenInBrowser -or $FindAllWebInterfaces) { $result | ForEach-Object { Start-Process -FilePath "http://$_" } } $result } |