Remotely Get CDP Neighbor
Click here to download and view the file.
Function: This script remotely captures the CDP multicast advertisement of the nearest Cisco switch and reports the switch port, vlan, switch ip address, etc.
If the switch is a Cisco phone, it retrieves the CDP information from the phone or opens a web browser with the CDP Neighbor info of the phone.
Requirements: This script requires an elevated PowerShell console.
The initiating user must have Admin permissions on the remote PC.
Usage: From an elevated PowerShell console, run: .\GetCdpNeighbor.ps1 <computername>
Output: The script will spend about 60 seconds capturing network traffic on the remote pc.
The script will then report the CDP Neighbor information.
If the CDP Neighbor of the PC is a Cisco Phone, the script will then retrieve the CDP info from the phone or open a web browser with the CDP Neighbor Port highlighted (if the XML port info page on the phone is not available).
GetCdpNeighbor.ps1
# Locally or remotely retrieve CDP Neighbor information for a PC
#
# Usage (Locally) - .\GetCdpNeighbor.ps1
# Usage (Remotely) - .\GetCdpNeighbor.ps1 <computername>
#
# If the script is given a PC name or IP address as an argument, it will run the script on the remote PC. If no argument is given, it will run locally.
#
# Much stolen from https://www.powershellgallery.com/packages/PSDiscoveryProtocol/1.0.0/Content/PSDiscoveryProtocol.psm1, http://www.rhyshaden.com/cdp.htm
# ScriptBlock with the functional code
[scriptblock]$sb = {
# Setup ETW
If (Test-Path ".\temp.etl") {
Remove-Item ".\temp.etl"
}
$t = New-Item -Path ".\temp.etl" -ItemType File
$a = Get-NetAdapter -Physical | Where { $_.Status -eq 'Up' -and $_.InterfaceType -eq 6 } | Select -First 1 -Expand Name
$ns = Get-NeteventSession -Name CDP 2>$Null
If ($ns) {
If ($ns.SessionStatus -eq "Running") { Stop-NetEventSession -Name CDP }
Remove-NetEventSession -Name CDP
}
$s = New-NetEventSession -Name CDP -LocalFilepath $t -CaptureMode SaveToFile
$ma = "01-00-0c-cc-cc-cc" # MAC address of the CDP multicast announcment
$cl = 61 # Capture Length (Time (in seconds) of the packet capture). CDP packets are sent every 60 seconds.
Add-NetEventPacketCaptureProvider -SessionName $s.Name -LinkLayerAddress $ma -TruncationLength 1024 -CaptureType BothPhysicalAndSwitch | Out-Null
Add-NetEventNetworkAdapter -Name $a -PromiscuousMode $True | Out-Null
# Start the packet capture
Start-NetEventSession -Name $s.Name
# Capture until capture length ($cl) expires
$end = (Get-Date).AddSeconds($cl)
While ($end -gt (Get-Date)) {
$left = $end.Subtract((Get-Date)).TotalSeconds
$perc = ($cl - $left) / $cl * 100
Write-Progress -Activity "CDP Packet Capture" -Status "Capturing Packets..." -SecondsRemaining $left -PercentComplete $perc
Start-Sleep 1
}
# Stop the packet capture
Stop-NetEventSession -Name $s.Name
# Read the temp.etl file created (ETW saves data in eventlog files). The CDP packet gets recorded as event ID 1001 and Logical-Link Control PID (protocol identifier) of 8192 ([UInt16]0x2000).
$log = Get-Winevent -Path $t -Oldest | Where { $_.Id -eq 1001 -and [UInt16]0x2000 -eq [BitConverter]::ToUInt16($_.Properties[3].Value[21..20], 0) } | Select -Last 1 -Expand Properties
# Cleanup ETW
Remove-NetEventSession -Name $s.Name
Start-Sleep -Seconds 2
Remove-Item -Path $t
# Extract packet data from log
If ($log) {
$packet = $log[3].Value
}
# Parse the packet for the CDP info
$offset = 26
$hash = @{}
While ($offset -lt ($packet.Length -4)) {
$type = [BitConverter]::ToUInt16($packet[($offset + 1)..$offset], 0)
$len = [BitConverter]::ToUInt16($packet[($offset + 3)..($offset + 2)], 0)
Switch ($type) {
1 { $hash.Add('Hostname', [System.Text.Encoding]::ASCII.GetString($packet[($offset + 4)..($offset + $len)])) }
2 { $hash.Add('IPAddress', ([System.Net.IPAddress][byte[]]$packet[($offset + 13)..($offset + 16)]).IPAddressToString) }
3 { $hash.Add('Port', [System.Text.Encoding]::ASCII.GetString($packet[($offset + 4)..($offset + $len)])) }
6 { $hash.Add('Switch', [System.Text.Encoding]::ASCII.GetString($packet[($offset + 4)..($offset + $len)])) }
10 { $hash.Add('VLAN', [BitConverter]::ToUInt16($packet[($offset + 5)..($offset + 4)], 0)) }
}
$offset = $offset + $len
}
# Output CDP info
Write-Host -Fore Cyan "`nComputer:"
Write-Host "--------------------------------------------------------------------" -NoNewLine
$hash | FT -HideTableHeaders
# If the CDP Neighbor is a phone...
If ($hash.Hostname -like "SEP*") {
$phone = $hash.IPAddress
$phUrl = "$phone/DeviceInformationX"
$poUrl = "$phone/PortInformationX"
$dirBase = "https://<CCM IP or Hostname>:8443/ccmcip/xmldirectorylist.jsp?"
# If the XML page with the CDP info on the phone is functional...
Try {
[xml]$phoneInfo = Invoke-WebRequest -Uri $phUrl -UseBasicParsing
[xml]$portInfo = Invoke-WebRequest -Uri $poUrl -UseBasicParsing
$dirUrl = $dirBase + "f=&l=&n=" + [string]($phoneInfo.DeviceInformation.phoneDN)
[xml]$phoneName = Invoke-WebRequest -Uri $dirUrl -UseBasicParsing
}
Catch {}
If ($phoneInfo) {
$phash = @{}
$phash.Add('Phone Number', $phoneInfo.DeviceInformation.phoneDN)
$phash.Add('Phone Name', $phoneName.CiscoIPPhoneDirectory.DirectoryEntry.Name)
$phash.Add('Port', $portInfo.PortInformation.CDPNeighborPort)
$phash.Add('Hostname', $portInfo.PortInformation.CDPNeighborDeviceID)
$phash.Add('IPAddress', $portInfo.PortInformation.CDPNeighborIP)
Write-Host -Fore Cyan "Phone:"
Write-Host "--------------------------------------------------------------------" -NoNewLine
$phash | FT -HideTableHeaders
}
# If the XML page with the CDP info on the phone is NOT functional...
Else {
Start "http://$phone/PortInformation?1#:~:text=CDP%20Neighbor%20Port"
}
}
}
# Determine whether to run locally or on a remote PC
If ($args) {
[string]$pc = $args.ToUpper()
$online = (Test-NetConnection -Computer $pc).PingSucceeded
If ($online) {
Invoke-Command -Computer $pc -ScriptBlock $sb
}
Else {
Write-Host -Fore Yellow "`n$pc" -NoNewLine
Write-Host " - appears to be offline.`n"
}
}
Else { Invoke-Command -ScriptBlock $sb }
This script consists of a ScriptBlock and a section of logic that determines whether to run locally or remotely. The ScriptBlock is the meat of the script and begins by setting up an ETW (Event Tracing for Windows) packet capture session. The capture will take 61 seconds (to insure that at least one of the CDP advertisements is captured).
After the capture is complete and cleaning up the ETW session, the script continues by reading the event log created and extracting the CDP packet. I would encourage you to grab a CDP packet in Wireshark and walk through the parsing process. The parsing is stolen entirely from here and walking through the packet myself shed a great deal of light on parsing process. Along with information here this will help you when troubleshooting your own version.
After parsing the packet and grabbing the details, the script determines if the computer's CDP neighbor is a Cisco phone and if it is, it then queries the phone for its CDP neighbor information and outputs the details of both. In the process of writing this script, I discovered one phone whose DeviceInformationX REST interface was not functional, so the script verifies that functionality and if that interface is broken on the phone, it then opens a web browser to the normal DeviceInformation page with the CDP Neighbor Port highlighted.