NetNeighbor Watch: The PowerShell Alternative To Arpwatch

In this post, we are going to setup NetNeighbor Watch on a Raspberry Pi. NetNeighbor Watch can keep an eye on your network and send you an email when a new host is discovered. NetNeighbor Watch is done completely in PowerShell. The results are very similar to those of arpwatch. NetNeighbor Watch is for anyone that wants more visibility into the wireless or wired devices on their network. We will also setup a weekly email report with all of the known hosts on your network. In this post, I will walk you through the entire process of setting this up from scratch on a Raspberry Pi, lets get started!


Prerequisites
  • Raspberry Pi with ethernet connectivity. A virtual machine or bare metal machine may also work, but that isn’t covered in this post.
  • Micro SDHC Card (8 GB minimum)
  • Windows 10 PC is needed to install Windows 10 IoT Core Dashboard
  • Be a member of the Windows Insider program
  • Gmail account

Gmail App Password

In order to send an email via Gmail, we need to setup an App Password. An App Password is a 16-digit passcode that gives a less secure app or device permission to access your Google Account. App Passwords can only be used with accounts that have 2-Step Verification turned on.

  1. Go to your Google Account.
  2. Select Security.
  3. Under “Signing in to Google,” select App Passwords. You may need to sign in. If you don’t have this option, it might be because:
    • 2-Step Verification is not set up for your account.
    • 2-Step Verification is only set up for security keys.
    • Your account is through work, school, or other organization.
    • You turned on Advanced Protection.
  4. At the bottom, choose Select app and choose Other (Custom name).
  5. Enter a custom name in the field (e.g., RaspberryPi, NetNeighbor, etc.).
  6. Click the GENERATE button.
  7. The 16 digit password will be displayed, copy it to a safe place. Once you click DONE you will not have visibility of this password again.
  8. Click DONE.

NetNeighbor Watch Code

GitHub

Let’s take a peek at the code involved to setup NetNeighbor Watch. First, we will declare the variables used for the script. Make sure that you input the information specific to your network. Modify the subnet variable to reflect the subnet in your network. You can change the range of IP Addresses to scan by adjusting the start and end variables. In this example, we are saving the known host csv file in the same directory the script will run from; this can be changed to any location you would like. The transcript files are also saved in the same directory the script runs from. The variables From, To, User and Password will need to be modified to match your Gmail account info. The password variable is the Gmail app password you setup earlier in this post. For simplicity of this post, I have the password in plain text but a better way is to use AES to encrypt passwords. Click HERE for a very good post on setting up AES to encrypt passwords. Next, we do some cleanup on the transcript files to ensure we don’t fill up the Micro SDHC card in the Raspberry Pi. Finally, we start the transcript to record the results of the script.

$subnet = "10.0.0" ### Subnet that you want to scan for new hosts
$Gateway = Get-NetRoute -DestinationPrefix 0.0.0.0/0 | Select-Object -ExpandProperty NextHop ### Default gateway IP address pulled from the Default Route
$start = 0 ### The starting number for the last octect of the subnet you want to scan for new hosts
$end = 255 ### The ending number for the last octect of the subnet you want to scan for new hosts
$Known_Hosts_File = $PSScriptRoot + "\NetNeighbors.csv" ### Location for the known hosts csv file, located in the same directory as this script
$Time = Get-Date -Format "MM-dd-yyyy-HHmm" ### Getting the date and formatting it to be used in the transcript file name
$Transcript_File = $PSScriptRoot + "\NetNeighbor_Watch_$Time.txt" ### Location for the transcript file, located in the same directory as this script
$From = "NetNeighbor Watch <demo@gmail.com>" ### The email address to use to send the emails
$To = "demo@gmail.com" ### The email address to receive the emails
$SMTPServer = "smtp.gmail.com" ### smtp server
$SMTPPort = "587" ### smtp sever port number
$User = "demo" ### user id of the user to send the emails
$Password  = convertto-securestring "demopassword" -asplaintext -force ### converting plain text password to secure string
$creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $Password ### putting together the PSCredential

### Cleaning up the old transcripts, only keeping the last 300 files which is about 7 days
Get-ChildItem $PSScriptRoot -Recurse -Include NetNeighbor_Watch*.txt | Where-Object {-not $_.PsIsContainer} | Sort-Object LastWriteTime -desc | Select-Object -Skip 300 | Remove-Item -Force

### Starting the transcript
Start-Transcript -Path $Transcript_File -Force

Now let’s dive into the functions. The first function sends an email with the supplied information via Send-MailMessage.

Function Send_Mail ($Body, $Subject) {
    Write-Host "[Sending Email] ($Subject) [$BODY]"
    Send-MailMessage -From $From -to $To -Subject $Subject -Body $Body -SmtpServer $SMTPServer -port $SMTPPort -UseSsl -Credential $creds -WarningAction Ignore
}

The second function is used to conduct a ping sweep on the subnet specified. It will conduct the while loop until the start variable is greater than the end variable. Inside the while loop is the Test-Connection command which determines whether a particular computer can be contacted across an IP network. By conducting the ping sweep we are essentially forcing an update to the ARP table.

Function Ping_Sweep {
    Write-Host "[Ping Sweep]"
    while ($start -le $end) { ### Conduct the while loop until the start variable is greater than the end variable
        $IP = "$subnet.$start" ### Combining the subnet variable with the start variable to create the IP address to ping
        $Connected = Test-Connection -ComputerName $IP -count 1 -Quiet -Delay 1 -TimeoutSeconds 1 ### Pinging the given IP address
        If ($Connected -eq $True){ ### If the IP is pingable then it will output that the IP is up
            Write-Host "[UP] $IP"
        }
        $start++ ### Incrementing the start variable by 1 each time through the loop
    }
}

The next function pulls the Address Resolution Protocol (ARP) cache via the Get-NetNeighbor command. Here we are only returning IPv4 addresses that are either in a stale or reachable state. The results are also limited to IP addresses that are in the given subnet.

Function NetNeighbors {
    Write-Host "[Identifying Net Neighbors]"
    ### Pulling Address Resolution Protocol (ARP) cache for only IPv4 addresses in a stale or reachable state and for only IP addresses that are in the given subnet
    $Neighbors = Get-NetNeighbor -AddressFamily IPv4 -State Stale, Reachable | Select-Object IPAddress, LinkLayerAddress | Where-Object {$_.IPAddress -match $subnet}
    Return $Neighbors
}

The Known_Hosts function imports the known hosts csv file. This file stores all of the previously discovered hosts.

Function Known_Hosts {
    Write-Host "[Importing Known Hosts]"
    ### Importing the known hosts csv file to be used for comparing against the ARP cache
    $Known_Hosts = Import-Csv -Path $Known_Hosts_File 
    Return $Known_Hosts
}

The Compare_Results function compares the results from the NetNeighbors function against the Known_Hosts function. We use the Compare-Object command to do the comparison between the two results. This also uses the Resolve-DnsName command to attempt to resolve IP Address to DNS Name. If there is a new host in the NetNeighbors function that isn’t in the Known_Hosts, then the script will send an email with those details and it will add that host to the known hosts csv file. Also, if there is a different IP Address for a known Link Layer Address (MAC Address) this will also trigger an email alert which would catch any mac spoofing.

Function Compare_Results ($NetNeighbors, $Known_Hosts) {
    Write-Host "[Comparing Net Neighbors and Known Hosts for differences]"
    ### Comparing the known hosts to the ARP cache for any newly discovered hosts
    $Results = Compare-Object -ReferenceObject $Known_Hosts -DifferenceObject $NetNeighbors -Property IPAddress, LinkLayerAddress 
    If ($Null -eq $Results){Write-Host "[No Change] Net Neighbors and Known Hosts Match, nothing to do here..."}
    Else{ ### If there is a new host discovered it will be processed
        Foreach ($Result in $Results){
            If ($Result.SideIndicator -eq '=>'){ ### If the sideindicator equals => it means that a new host was present in the NetNeighbors
                ### Creating a PSCustomObject with the IPAddress, LinkLayerAddress and Date
                $outputcsv = [PSCustomObject] @{
                IPAddress = $Result.IPAddress
                LinkLayerAddress = $Result.LinkLayerAddress
                Date_Discovered = (Get-Date)
                }
                ### Appending the new host information to the known hosts file so that we won't get alerted on this host again
                $outputcsv | Export-Csv -Path $Known_Hosts_File -Append -NoClobber
                ### Attempting to resolve the IP Address to Name to include in the email alert
                $DNS_Name = Resolve-DnsName $($Result.IPAddress) -QuickTimeout -ErrorAction SilentlyContinue | Select-Object -ExpandProperty NameHost
                ### If there is no DNS name for that IP address it will set the variable to "DNS record does not exit"
                If ($null -eq $DNS_Name){$DNS_Name = "DNS record does not exist"}
                ### Calling the Send_Mail function with the new host specific information
                Send_Mail -Body "$($Result.IPAddress) / $($Result.LinkLayerAddress) / $DNS_Name" -Subject "[NetNeighbor Watch] Alert: New Host Detected"
            }
            If ($Result.SideIndicator -eq '<='){ ### If the sideindicator equals <= it means that a host was present in the known host csv file but not in ARP cache
                Write-Host "[Offline Host] $($Result.IPAddress) / $($Result.LinkLayerAddress) is present in the known hosts csv file but not in ARP cache"
            }
        }
    }
}

The Pre_Check function ensures that the Known Host csv file is present and formatted correctly. It adds the default gateway IP Address as the first item in the csv file to avoid any issues with comparing a blank file.

Function Pre_Check {
    If (-not (Test-Path $Known_Hosts_File)) {  ### if there is not a known host csv file, the script will create one
        Write-Host "[Pre Check] No csv file present, creating one..."
        ### Grabbing the LinkLayerrAddress of the default gateway so that it can populate the known host csv file
        $Gateway = Get-NetNeighbor -AddressFamily IPv4 -State Stale, Reachable | Select-Object IPAddress, LinkLayerAddress | Where-Object {$_.IPAddress -eq $Gateway}
        ### Creating a PSCustomObject with the IPAddress, LinkLayerAddress and Date
        $outputcsv = [PSCustomObject] @{
        IPAddress = $Gateway.IPAddress
        LinkLayerAddress = $Gateway.LinkLayerAddress
        Date_Discovered = (Get-Date)
        }
        ### writing the default gateway information to the known hosts file
        $outputcsv | Export-Csv -Path $Known_Hosts_File
    }
    Else{ ### If there is a known host csv file, the script will see if its blank
        $Import_Check = Import-Csv -Path $Known_Hosts_File ### importing the known host csv file
        If ($null -eq $Import_Check){ ### if the known host csv file is blank it will populat it with the default gateway
            Write-host "[Empty CSV File, script will attempt to populate it with the gateway]" -BackgroundColor Red -ForegroundColor White
            Send_Mail -Body "Empty CSV File, script will attempt to populate it with the gateway." -Subject "[NetNeighbor Watch] Error Triggered"
            ### Grabbing the LinkLayerrAddress of the default gateway so that it can populate the known host csv file
            $Gateway = Get-NetNeighbor -AddressFamily IPv4 -State Stale, Reachable | Select-Object IPAddress, LinkLayerAddress | Where-Object {$_.IPAddress -eq $Gateway}
            ### Creating a PSCustomObject with the IPAddress, LinkLayerAddress and Date   
            $outputcsv = [PSCustomObject] @{
            IPAddress = $Gateway.IPAddress
            LinkLayerAddress = $Gateway.LinkLayerAddress
            Date_Discovered = (Get-Date)
            }
            ### writing the default gateway information to the known hosts file
            $outputcsv | Export-Csv -Path $Known_Hosts_File -Force
        }
    }
}

Finally, we get the section of the script that calls the functions and sends an email if an error occurs.

try{
    Pre_Check
    Ping_Sweep
    $NetNeighbors = NetNeighbors
    $Known_Hosts = Known_Hosts
    Compare_Results -NetNeighbors $NetNeighbors -Known_Hosts $Known_Hosts 
}
Catch [Exception]{ ### If the command inside the try statement fails the error will be outputted
    $errormessage = $_.Exception.Message
    Write-host $errormessage -BackgroundColor Red -ForegroundColor White
    Send_Mail -Body "$errormessage" -Subject "[NetNeighbor Watch] Error Triggered"
}

Stop-Transcript

NetNeighbor Report Code

GitHub

Now it’s time to dig into the code involved to setup the NetNeighbor Report. First, we will declare the variables used for the script. Just like the previous script input the information specific to your environment.

$Known_Hosts_File = $PSScriptRoot + "\NetNeighbors.csv" ### Location for the known hosts csv file, located in the same directory as this script
$Time = Get-Date -Format "MM-dd-yyyy-HHmm" ### Getting the date and formatting it to be used in the transcript file name
$Transcript_File = $PSScriptRoot + "\NetNeighbor_Report_$Time.txt" ### Location for the transcript file, located in the same directory as this script
$From = "NetNeighbor Watch <demo@gmail.com>" ### The email address to use to send the emails
$To = "demo@gmail.com" ### The email address to receive the emails
$SMTPServer = "smtp.gmail.com" ### smtp server
$SMTPPort = "587" ### smtp sever port number
$User = "demo" ### user id of the user to send the emails
$Password  = convertto-securestring "demopassword" -asplaintext -force ### converting plain text password to secure string
$creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $Password ### putting together the PSCredential

### Cleaning up the old transcripts, only keeping the last 10 files which is 10 weeks
Get-ChildItem $PSScriptRoot -Recurse -Include NetNeighbor_Report*.txt| Where-Object {-not $_.PsIsContainer} | Sort-Object LastWriteTime -desc | Select-Object -Skip 10 | Remove-Item -Force

### Starting the transcript
Start-Transcript -Path $Transcript_File -Force

The first function sends an email with an attachment and the body as HTML via Send-MailMessage. This function is used to send the report email with the last modified transcript file attached.

Function Send_Mail_HTML ($Body, $Subject, $Attachment) {
    Write-Host "[Sending Email] ($Subject) [$BODY]"
    Send-MailMessage -Attachments $Attachment -BodyAsHtml -From $From -to $To -Subject $Subject -Body $Body -SmtpServer $SMTPServer -port $SMTPPort -UseSsl -Credential $creds -WarningAction Ignore
}

The second function sends an email with the supplied information via Send-MailMessage. This will be utilized if any errors crop up.

Function Send_Mail ($Body, $Subject) {
    Write-Host "[Sending Email] ($Subject) [$BODY]"
    Send-MailMessage -From $From -to $To -Subject $Subject -Body $Body -SmtpServer $SMTPServer -port $SMTPPort -UseSsl -Credential $creds -WarningAction Ignore
}

The main function for this script is the Known_Hosts_Report. This function imports the known host csv file then sorts it by IP Address. We count how many hosts are in the csv file so there is a very easy reference point to compare against previous reports. Then we build the HTML header and the table headers for the report. Next it goes through each known host and attempts to resolve the DNS name, then appends to the html_table variable. Then it gathers the latest transcript log from NetNeighbor_Watch.ps1 and sends it as an attachment in the report email. Reviewing the transcript log from NetNeighbor Watch ensures that the automated script is functioning as expected.

Function Known_Hosts_Report {
    Write-Host "[Importing known hosts csv file]"
    ### Importing the known hosts csv file to be used for the report, then sorting by IP address
    $Known_Hosts = Import-Csv -Path $Known_Hosts_File | Sort-Object { $_.IPAddress -as [Version]}
    ### Measuring how many hosts are present in the csv file
    $Known_Hosts_Count = $Known_Hosts | Measure-Object | ForEach-Object {$_.Count}
    Write-Host "[$Known_Hosts_Count host(s) in known hosts file]"
    #############################################################################################
    ### Building the HTML header and table column titles
    #############################################################################################
    $html_header = "
    <html>
    <head>
    <meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1'>
    <title>NetNeighbor Watch Report</title>
    <STYLE TYPE='text/css'>
    </style>
    </head>
    <body>
    <table-layout: fixed>
    <table width='100%'>
    <tr bgcolor='#4682B4'>
    <td colspan='7' height='25' align='center'><strong><font color='white' size='4' face='tahoma'>NetNeighbor Watch Report: $Known_Hosts_Count host(s)</font>
    </tr>
    </table>
    <table width='100%'>
    <tr bgcolor='#CCCCCC'>
    <td colspan='7' height='20' align='center'><strong><font color='black' size='2' face='tahoma'> Report of all known hosts that NetNeighbor Watch has discovered </font>
    </tr>
    </table>
    <table width='100%'><tbody>
        <tr bgcolor=black>
        <td width='25%' height='15' align='center'> <strong> <font color='white' size='2' face='tahoma' >IP Address</font></strong></td>
        <td width='25%' height='15' align='center'> <strong> <font color='white' size='2' face='tahoma' >MAC Address</font></strong></td>
        <td width='25%' height='15' align='center'> <strong> <font color='white' size='2' face='tahoma' >DNS Name</font></strong></td>
        <td width='25%' height='15' align='center'> <strong> <font color='white' size='2' face='tahoma' >Date Discovered</font></strong></td>
        </tr>
    </table>
    "

    #############################################################################################
    ### Going through each known host and attempting to resolve the dns name, then appending to the html_table variable
    #############################################################################################
    Foreach ($Known_Host in $Known_Hosts){
        Write-Host "[Processing: $($Known_Host.IPAddress)]"
        ### Attempting to resolve the IP Address to Name 
        $DNS_Name = Resolve-DnsName $($Known_Host.IPAddress) -QuickTimeout -ErrorAction SilentlyContinue | ForEach-Object {$_.NameHost}
        ### If there is no DNS name for that IP address it will set the variable to "DNS record does not exit"
        If ($null -eq $DNS_Name){$DNS_Name = "DNS record does not exist"}
        ### If htmlwrite_count variable is an even number the html background color for that row will be off-white, odd number will be gray
        $htmlwrite_count | ForEach-Object {if($_ % 2 -eq 0 ) {$htmlbgcolor = '<tr bgcolor=#F5F5F5>'} } ## Even Number (off-white)
        $htmlwrite_count | ForEach-Object {if($_ % 2 -eq 1 ) {$htmlbgcolor = '<tr bgcolor=#CCCCCC>'} } ## Odd Number (gray)
        #### Creating the HTML rows
        $html_table += "
        <table width='100%'><tbody>
            $htmlbgcolor
            <td width='25%' align='center'>$($Known_Host.IPAddress)</td>
            <td width='25%' align='center'>$($Known_Host.LinkLayerAddress)</td>
            <td width='25%' align='center'>$DNS_Name</td>
            <td width='25%' align='center'>$($Known_Host.Date_Discovered)</td>
            </tr>
        </table>
        "
        $htmlwrite_count++ ### Incrementing the count by 1 so that the next HTML row is a different color
    }
    #############################################################################################
    ### Gathering the latest transcript log from NetNeighbor_Watch.ps1 and sending email with the html report
    #############################################################################################
    ### Selecting the last modified transcript file
    Write-Host "[Gathering last modified transcript]"
    $Recent_Log = Get-ChildItem $PSScriptRoot -Recurse -Include NetNeighbor_Watch*.txt | Where-Object {-not $_.PsIsContainer} | Sort-Object LastWriteTime | Select-Object -Last 1
    ### Reading the content of the transcript file and sending it to NetNeighbor_Transcript.txt
    Get-Content -Path $Recent_Log -Raw | Out-File -FilePath $PSScriptRoot\NetNeighbor_Transcript.txt -Force
    ### Putting the html peices together
    $html_email = $html_header + $html_table
    ### Sending email with html report and last transcript attached
    Send_Mail_HTML -Body $html_email -Subject "[NetNeighbor Watch] Report: $Known_Hosts_Count host(s)" -Attachment $PSScriptRoot\NetNeighbor_Transcript.txt
}

Finally, we call the function and catch any errors that occur.

try{
    Known_Hosts_Report
}
Catch [Exception]{ ### If the command inside the try statement fails the error will be outputted
    $errormessage = $_.Exception.Message
    Write-host $errormessage -BackgroundColor Red -ForegroundColor White
    Send_Mail -Body "$errormessage" -Subject "[NetNeighbor Watch] Error Triggered"
}

Stop-Transcript

Raspberry Pi 3 Model B+ Setup

Now that we have the scripts put together, we need to create a platform to run them on. That’s where the Raspberry Pi comes into play. I utilized a Raspberry Pi 3 Model B+ for this project, but other models of Raspberry Pi should work as well. If you have a different model of Raspberry Pi, it may be a slightly different process to get Windows 10 IoT Core setup. The following steps need to be done on a Windows 10 PC.

  1. Download and install Windows 10 IoT Core Dashboard
  2. Download Windows 10 IoT Core ISO
    • Login with credentials that are a member of the Windows Insider Program to the below site.
    • https://www.microsoft.com/en-us/software-download/windowsiot?wa=wsignin1.0#!
    • Scroll down and Click on RaspberryPi 3B+ Technical Preview Build 17661 to begin the download
    • Once Windows10_InsiderPreview_IoTCore_RPi3B_en-us_17661.iso is downloaded, double click on the iso file to mount it.
    • Install the Windows_10_IoT_Core_RPi3B+.msi located in the mounted iso file, accept all defaults.
  3. Open Windows 10 IoT Core Dashboard from the start menu
  4. Set up a new device
    • Click on Set up a new device in the menu on the left
    • Select Broadcomm [Raspberry Pi 2 & 3] from the drop down for “Device type”
    • Select Custom from the drop down for “OS Build”
    • Click on the Browse button under “Flash the pre-downloaded image file (Flash.ffu) to the SD Card”
    • Browse to C:\Program Files (x86)\Microsoft IoT\FFU\RaspberryPi2 and select the flash.ffu file and click Open
    • Insert the Micro SDHC card into your PC and make sure its selected for the “Drive”
    • Give your new device a name
    • Enter in an Administrator password twice
    • Check the box for “I accept the software and license terms”
    • Click on Install
    • Click on Continue to “Erasing the SD Card”
    • Click Yes on the User Account Control window
    • A cmd.exe window will appear, wait for it to complete
    • Once the image process is complete, remove the Micro SDHC card
  5. Insert the Micro SDHC card into the Raspberry Pi
  6. I recommend plugging the Raspberry Pi into a monitor so you can see the progress of the OS install, it can take a while for this process to finish.
  7. It’s optional to plug in the ethernet cable at this point, but if you can have both a monitor and ethernet cable plugged in at the same time, go for it.
  8. Plug in power cable and watch the OS install. If it fails to boot, you may need to try a different Micro SDHC.
  9. Once the OS has fully installed, you can disconnect from the monitor and ensure you have it plugged into ethernet.
  10. Open Windows 10 IoT Core Dashboard
  11. Click on My devices on the left side menu
  12. If everything went as expected, you should see your Raspberry Pi on the right side, this can take a few minutes to discover initially.
  13. Verify your device is functioning properly by right clicking your device and select Open in Device Portal.
  14. Launch PowerShell to the Raspberry Pi by right clicking your device and select Launch PowerShell, provide the Administrator password when prompted. This can take a little bit of time, so be patient. Once connected you can exit that shell.
  15. Take note of the IPv4 Address your device has as we will use this later. Ideally you want to configure a DHCP Reservation or statically assign an IP Address to ensure the IP Address never changes.
  16. Download Powershell 7 for Windows Arm 32 Bit from: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/PowerShell-7.0.3-win-arm32.zip
  17. I am going to utilize PowerShell 7.0.3 / Windows Terminal on my Windows 10 PC for the remainder of these steps.
  18. Add the Raspberry Pi to your Trusted Hosts (use the IPv4 Address of your Raspberry Pi)
    • Right click Windows Terminal and select Run as administrator
    • Set-Item -Path WSMan:\localhost\Client\TrustedHosts 10.0.0.200
    • Hit enter twice
  19. Create a PSSession to your Raspberry Pi
    • $S = New-PSSession -ComputerName 10.0.0.200 -Credential Administrator
    • Enter the password you set for Administrator
  20. Copy the PowerShell zip file to the Raspberry Pi
    • Browse to the location you downloaded the zip file to (example: Set-Location C:\users\admin\downloads)
    • Copy-Item .\PowerShell-7.0.3-win-arm32.zip -Destination u:\Programs\PowerShell7.zip -ToSession $s
  21. Connect to the Raspberry Pi by entering the PSSession
    • Enter-PSSession $S
  22. Install PowerShell 7 on the Raspberry Pi
    • Set-Location u:\Programs
    • Expand-Archive .\PowerShell7.zip
    • Set-Location .\PowerShell7
    • .\Install-PowerShellRemoting.ps1 -PowerShellHome .
    • You will see the following message if successful:
      Restarting WinRM to ensure that the plugin configuration change takes effect. This is required for WinRM running on Windows SKUs prior to Windows 10. Processing data for a remote command failed with the following error message: The I/O operation has been aborted because of either a thread exit or an application request. For more information, see the about_Remote_Troubleshooting Help topic.
  23. Connect back to the Raspberry Pi via PowerShell 7
    • Enter-PSSession -ComputerName 10.0.0.200 -Credential Administrator -Configuration powershell.7.0.3
    • Enter the password you set for Administrator
    • Confirm the PowerShell version is 7
      • $psversiontable
  24. Copy required PowerShell modules and Scripts
    • Open a new terminal tab or exit current PSSession
    • Create a new PSSession with PowerShell version 7
      • $S = New-PSSession -ComputerName 10.0.0.200 -Credential Administrator -Configuration powershell.7.0.3
      • Enter the password you set for Administrator
    • Locate and copy the NetTCPIP and DnsClient PowerShell modules from your Windows 10 PC to the Raspberry Pi
      • Get-Module -ListAvailable -Name DnsClient | Select-Object Name, Path
      • Get-Module -ListAvailable -Name NetTCPIP | Select-Object Name, Path
      • You can copy the entire directory for both DnsClient and NetTCPIP to a directory in your profile for ease of use or just directly from its current location
      • Copy-Item .\NetTCPIP -Destination u:\Programs\PowerShell7\Modules\NetTCPIP -ToSession $s -Recurse
      • Copy-Item .\DnsClient -Destination u:\Programs\PowerShell7\Modules\DnsClient -ToSession $s -Recurse
    • Copy the two scripts we created earlier into a scripts directory and then we will copy the entire directory to the Raspberry Pi.
      • Copy-Item .\Scripts -Destination u:\Programs\Scripts -ToSession $s -Recurse
  25. Import NetTCPIP and DnsClient modules
    • Enter-PSSession $s or switch back to your other Terminal tab
    • List all the available modules to import, ensure you see NetTCPIP and DnsClient
      • Get-Module -ListAvailable
    • Import-Module NetTCPIP
    • Import-Module DnsClient
  26. Create Scheduled Task to run every 30 minutes for the NetNeighbor Watch script
    • schtasks /create /sc minute /mo 30 /st 13:15 /tn NetNeighbor /RU system /tr “U:\Programs\PowerShell7\pwsh.exe -ExecutionPolicy Bypass U:\Programs\Scripts\NetNeighbor_Watch.ps1”
  27. Create Scheduled Task to run every Sunday at 13:00 for the NetNeighbor Report script
    • schtasks /create /sc weekly /mo 1 /d SUN /st 13:00 /tn NetNeighborReport /RU system /tr “U:\Programs\PowerShell7\pwsh.exe -ExecutionPolicy Bypass U:\Programs\Scripts\NetNeighbor_Report.ps1”
  28. Check that the scheduled tasks were created successfully
    • schtasks /query /fo LIST /v /TN NetNeighbor
    • schtasks /query /fo LIST /v /TN NetNeighborReport
  29. Manually run the scheduled tasks to ensure everything works as expected
    • schtasks /Run /TN NetNeighbor
    • schtasks /Run /TN NetNeighborReport (wait until NetNeighbor has run through before running)
  30. Check transcript logs and the known hosts csv file to ensure everything is working
    • One method of viewing the logs on the Raspberry Pi is to use Windows Explorer to browse to the file path. You will be prompted for administrator credentials when doing so. The path would be similar to the following: \\10.0.0.200\u$\Programs\Scripts.
    • The transcript logs will be in the location that you specified in the script. If you left it the way I have it in the example script, then the logs are in the same directory as the script.
    • The known hosts csv file will be in the location that you specified in the script. If you left it the way I have it in the example script, then the csv file will be in the same directory as the script.

Final Results

Below is an example of what you’ll see in your Gmail inbox when a new host is detected with NetNeighbor Watch.

Below is an example of the email you will see from the weekly email from the NetNeighbor Report.


Resetting Known Hosts

If you want to start over with the known hosts in your network, you can do so at anytime. Just simply delete the NetNeighbors.csv from the Raspberry Pi and the next time the script runs it will create a new file and populate it with all active hosts in its ARP cache. If you left it the way I have it in the example script, then the csv file will be in the same directory as the script (\\10.0.0.200\u$\Programs\Scripts).

That’s it for now, thanks for reading!


Check out my other blog posts:

4 thoughts on “NetNeighbor Watch: The PowerShell Alternative To Arpwatch

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s