Find-LANHosts.psm1


<#
    .SYNOPSIS
    Quickly finds host on a local network
 
    .DESCRIPTION
    Accepts an array of IP Addresses, and uses ARP requests to determine whether a host is present on a network segment.
 
    As APR is a Layer 2 mechanism, the list of IP addesses need to be on the same network as the device running the script.
 
    .PARAMETER IP
    Specifies one or more IP addresses to scan for. Typically this will be a list of all usable hosts on a network
 
    .PARAMETER DelayMS
    Optional. Specifies the interpacket delay, default is 2ms. Can be increased if scanning unreliable or high latency networks.
 
    .PARAMETER ClearARPCache
    Optional. Clears the ARP cache before starting a scan. This is recommended, but may require elevated priviliges.
 
    .EXAMPLE
    $IPs = 1..254 | % {"10.250.1.$_"}; Find-LANHosts -IP $IPs
 
    .EXAMPLE
    $IPs = 1..254 | % {"192.168.0.$_"}; Find-LANHosts $IPs
 
    .EXAMPLE
    1..254 | % {"192.168.1.$_"} | Find-LANHosts -ClearARPCache
 
    .EXAMPLE
    1..254 | % {"10.1.1.$_"} | Find-LANHosts -DelayMS 5
 
    .LINK
    https://github.com/mdjx/PSLANScan
#>

function Find-LANHosts {
    [Cmdletbinding()]

    Param (
        [Parameter(Mandatory, ValueFromPipeline, Position = 1)]
        [string[]]$IP,

        [Parameter(Mandatory = $false, Position = 2)]
        [ValidateRange(0, 15000)]
        [int]$DelayMS = 2,
        
        [switch]$ClearARPCache
    )

    Begin {

        $ASCIIEncoding = New-Object System.Text.ASCIIEncoding
        $Bytes = $ASCIIEncoding.GetBytes("a")
        $UDP = New-Object System.Net.Sockets.Udpclient

        if ($ClearARPCache) {
            $ARPClear = arp -d 2>&1
            if (($ARPClear.count -gt 0) -and ($ARPClear[0] -is [System.Management.Automation.ErrorRecord]) -and ($ARPClear[0].Exception -notmatch "The parameter is incorrect")) {
                Throw $ARPClear[0].Exception
            }
        }

        $IPList = [System.Collections.ArrayList]@()
        $Timer = [System.Diagnostics.Stopwatch]::StartNew()
    }

    Process {
        $IP | ForEach-Object {
            $UDP.Connect($_, 1)
            [void]$UDP.Send($Bytes, $Bytes.length)
            [void]$IPList.Add($_)
            if ($DelayMS) {
                [System.Threading.Thread]::Sleep($DelayMS)
            }
        }
    }

    End {
    
        $Hosts = arp -a
        $Timer.Stop()

        if ($Timer.Elapsed.TotalSeconds -gt 15) {
            Write-Warning "Scan took longer than 15 seconds, ARP entries may have been flushed. Recommend lowering DelayMS parameter"
        }

        $Hosts = $Hosts | Where-Object {$_ -match "dynamic"} | % {($_.trim() -replace " {1,}", ",") | ConvertFrom-Csv -Header "IP", "MACAddress"}
        $Hosts = $Hosts | Where-Object {$_.IP -in $IPList}

        Write-Output $Hosts
    }
}