Discovery using Powershell
LLDP does one thing very well, it will tell you the port on a switch that device is connected to and some information around the switch configuration. Where this comes in handy is if you need to move a port to another VLAN or be able to trace a cable during switch upgrades etc… I searched everywhere for a Windows application that could do that and came up empty. Instead of getting discouraged I searched the web and found a command based script called LinkSkippy that ran a combination of cmd scripts that had some Powershell influence for specific functions. The guy who created it did a good job but it wasn’t what I wanted and his code was not really built for the outputs I required. It does CDP though, which isn’t something I need at this time but may incorporate later. At a basic level it uses pktmon which is a built in Microsoft utility that allows you to create .etl files and then you convert those to .txt so you can parse. That’s where the magic happens. I took the following code and wrapped it in a GUI to make it easier to run. Attached is a .zip file with all the code and two executables, these need to be run from an elevated administrative session. These are for my own use, and no warranty is implied, but you are welcome to use the code anyway you’d like.
LLDPDiscovery.ps1 has the GUI code in it.
LLDPDiscoveryNoGUI.ps1 is the raw script.
LLDPDiscovery.exe launches a powershell console for debugging.
LLDPDiscoveryNC.exe launches without a console.
## Variables
$sessionName = "LLDPListener"
$etlPath = "$env:TEMP\LLDPListener.etl"
$txtPath = "$env:TEMP\LLDPListener.txt"
## Functions
function Reset-Capture {
$NetEventSessions = (Get-NetEventSession | Select-Object Name).Name
foreach ($Session in $NetEventSessions) {
Stop-NetEventSession -Name $Session
Remove-NetEventSession -Name $Session
}
Remove-Item $etlPath, $txtPath -ErrorAction SilentlyContinue
pktmon stop > $null
pktmon filter remove > $null
}
function Start-Capture {
Write-Host "Starting LLDP capture session..."
$LLDPSession = New-NetEventSession -Name $sessionName -CaptureMode RealtimeLocal
$AddNetEventSession = Add-NetEventPacketCaptureProvider -SessionName $sessionName -Level 0x0 -CaptureType Physical -TruncationLength 128
$StartNetEventSession = Start-NetEventSession -Name $sessionName
pktmon filter add "LLDP" -d LLDP > $null
pktmon start --capture --type flow --pkt-size 0 --file-name $etlPath --comp nics > $null
}
function Parse-LLDPData {
param (
[string]$InputFile = ""
)
function Get-ComponentMACAddress {
param (
[string]$PktmonTxtPath
)
function Get-ComponentID {
param (
[string]$LogFilePath
)
# Read all lines from the log file
$logLines = Get-Content -Path $LogFilePath
# Initialize an array to store component IDs
$componentIds = @()
# Loop through each line and extract component IDs
foreach ($line in $logLines) {
if ($line -match 'Component\s+(\d+)') {
$componentIds += $matches[1]
}
}
# Return the second component ID if it exists
if ($componentIds.Count -ge 1) {
return $componentIds[0]
} else {
Write-Warning "Second component ID not found."
return $null
}
}
$ComponentID = Get-ComponentID -LogFilePath $PktmonTxtPath
$pktmonOutput = pktmon comp list
$ClientMACLookup = $pktmonOutput | Where-Object { $_ -match '^\s*\d+\s+[A-F0-9\-]+\s+.+$' } |
ForEach-Object {
$id = $_.Substring(0, 5).Trim()
$mac = $_.Substring(5, 19).Trim()
$name = $_.Substring(25).Trim()
[PSCustomObject]@{
Id = $id
MAC = $mac
Name = $name
}
}
$ClientMACAddress = ($ClientMACLookup | Where-Object { $_.Id -eq $ComponentID } | Select-Object MAC).MAC
return $ClientMACAddress
}
pktmon etl2txt $etlPath --out $txtPath --verbose 3 > $null
# Load LLDP data
if ($InputFile -and (Test-Path $InputFile)) {
$lldpData = Get-Content $InputFile -Raw
} else {
Write-Host "No input file provided or file not found. Please specify -InputFile."
return
}
$ClientMACAddress = Get-ComponentMacAddress -PktmonTxtPath $InputFile
# Initialize result hashtable
$results = @{}
# Split into lines
$lines = $lldpData -split "`n"
# Loop through lines and match TLVs
for ($i = 0; $i -lt $lines.Count; $i++) {
$line = $lines[$i].Trim()
if ($line -match "MAC address \(4\): ([\w\-]+)") {
$MACAddress = $results["MAC Address"] = $matches[1]
}
elseif ($line -match "Port ID TLV") {
$nextLine = $lines[$i + 1].Trim()
if ($nextLine -match "Subtype Local.*: (.+)$") {
$PortID = $results["Port ID"] = $matches[1].Trim()
}
}
elseif ($line -match "TTL (\d+)s") {
$TTL = $results["TTL"] = "$($matches[1]) seconds"
}
elseif ($line -match "Port Description TLV.*: (.+)") {
$PortDescription = $results["Port Description"] = $matches[1]
}
elseif ($line -match "System Name TLV.*: (.+)") {
$SystemName = $results["System Name"] = $matches[1]
}
elseif ($line -match "System Description TLV") {
$SystemDescription = $results["System Description"] = $lines[$i + 1].Trim()
}
elseif ($line -match "System\s+Capabilities\s+\[([^\]]+)\]") {
$SystemCapabilities = $results["System Capabilities"] = $matches[1]
}
elseif ($line -match "Enabled\s+Capabilities\s+\[([^\]]+)\]") {
$EnabledCapabilities = $results["Enabled Capabilities"] = $matches[1]
}
elseif ($line -match "port vlan id \(PVID\): (\d+)") {
$PortVLANID = $results["Port VLAN ID"] = $matches[1]
}
}
$LLDPOutput = @()
$LLDPOutputHashTable = New-Object PSObject
$LLDPOutputHashTable | Add-Member -MemberType NoteProperty -Name "SystemName" -Value $SystemName
$LLDPOutputHashTable | Add-Member -MemberType NoteProperty -Name "SystemDescription" -Value $SystemDescription
$LLDPOutputHashTable | Add-Member -MemberType NoteProperty -Name "SystemCapabilities" -Value $SystemCapabilities
$LLDPOutputHashTable | Add-Member -MemberType NoteProperty -Name "SystemEnabledCapabilities" -Value $EnabledCapabilities
$LLDPOutputHashTable | Add-Member -MemberType NoteProperty -Name "PortID" -Value $PortID
$LLDPOutputHashTable | Add-Member -MemberType NoteProperty -Name "VLANID" -Value $PortVLANID
$LLDPOutputHashTable | Add-Member -MemberType NoteProperty -Name "SwitchMACAddress" -Value $MACAddress
$LLDPOutputHashTable | Add-Member -MemberType NoteProperty -Name "ClientMACAddress" -Value $ClientMACAddress
$LLDPOutputHashTable | Add-Member -MemberType NoteProperty -Name "TTL" -Value $TTL
$LLDPOutput += $LLDPOutputHashTable
return $LLDPOutput
}
# Main Execution
Reset-Capture
Start-Capture
Write-Host "Listening for LLDP packets on Ethernet interfaces... (Press Ctrl+C to stop)"
do {
Start-Sleep -Seconds 2
$counters = pktmon counters
} while ($counters -match "All counters are zero.")
Write-Host "`nLLDP packet received!"
pktmon stop > $null
$LLDP = Parse-LLDPData -InputFile $txtPath
$ResetCapture = Reset-Capture
$SystemName = $LLDP.SystemName
$SystemDescription = $LLDP.SystemDescription
$SystemCapabilities = $LLDP.SystemCapabilities
$SystemEnabledCapabilities = $LLDP.SystemEnabledCapabilities
$PortID = $LLDP.PortID
$VLANID = $LLDP.VLANID
$SwitchMACAddress = $LLDP.SwitchMACAddress
$ClientMACAddress = $LLDP.ClientMACAddress
$TTL = $LLDP.TTL
Write-Host "-------------------- LLDP Information --------------------"
Write-Host " System Name: $SystemName"
Write-Host " System Description: $SystemDescription"
Write-Host " System Capabilities: $SystemCapabilities"
Write-Host "System Enabled Capabilities: $SystemEnabledCapabilities"
Write-Host " Port ID: $PortID"
Write-Host " VLAN ID: $VLANID"
Write-Host " Switch MAC Address: $SwitchMACAddress"
Write-Host " Client MAC Address: $ClientMACAddress"
Write-Host " TTL: $TTL"
Leave a Reply