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.