Private/Invoke-VBSwitchSNMPWalk.ps1
|
function Invoke-VBSwitchSNMPWalk { <# .SYNOPSIS Walk ARP, MAC, and ifAlias tables on a single managed switch via SNMP. Private helper used only by Get-VBSwitchARP. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$SwitchIP, [Parameter(Mandatory)] [string[]]$CommunityStrings, [Parameter()] [int]$TimeoutMs = 2000 ) # OIDs to walk $OID_ARP_TABLE = '1.3.6.1.2.1.4.22.1.2' # ipNetToMediaPhysAddress: IP -> MAC $OID_FDB_PORT = '1.3.6.1.2.1.17.4.3.1.2' # dot1dTpFdbPort: MAC -> port index $OID_IF_ALIAS = '1.3.6.1.2.1.31.1.1.1.18' # ifAlias: port index -> description $result = @{} # keyed by IP $snmp = $null $community = $null # Try community strings to find one that works foreach ($cs in $CommunityStrings) { $snmp = $null try { $snmp = New-Object -ComObject olePrn.OleSNMP -ErrorAction Stop $snmp.Timeout = $TimeoutMs $snmp.Open($SwitchIP, $cs, 2, $TimeoutMs) # Test with a quick get -- if it throws, community is wrong $null = $snmp.Get('1.3.6.1.2.1.1.1.0') $community = $cs break } catch { if ($snmp) { try { $snmp.Close() } catch { } try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($snmp) | Out-Null } catch { } $snmp = $null } } } if (-not $community) { Write-Warning "[$SwitchIP] No SNMP community string succeeded -- skipping this switch" return $result } try { # Walk ARP table: each entry is OID suffix = ifIndex.IP, value = MAC hex string $arpTable = @{} # IP -> MAC $arpWalk = $snmp.GetTree($OID_ARP_TABLE) if ($arpWalk) { foreach ($key in $arpWalk.Keys) { # OID suffix format: <ifIndex>.<a>.<b>.<c>.<d> e.g. 1.192.168.1.45 if ($key -match '\.(\d+\.\d+\.\d+\.\d+)$') { $ip = $Matches[1] $mac = $arpWalk[$key] $macNorm = ConvertTo-VBNormalisedMAC -MACAddress $mac if (-not [string]::IsNullOrWhiteSpace($macNorm)) { $arpTable[$ip] = $mac } } } } # Walk FDB port table: MAC -> bridge port index $macPortTable = @{} # MAC (no separators, uppercase) -> port index $fdbWalk = $snmp.GetTree($OID_FDB_PORT) if ($fdbWalk) { foreach ($key in $fdbWalk.Keys) { # OID suffix is MAC as decimal octets: 0.26.43.60.77.94 if ($key -match '((\d+\.){5}\d+)$') { $octets = $Matches[1] -split '\.' $macHex = ($octets | ForEach-Object { '{0:X2}' -f [int]$_ }) -join '' $portIndex = $fdbWalk[$key] $macPortTable[$macHex] = $portIndex } } } # Walk ifAlias table: port index -> description string $ifAliasTable = @{} # port index -> alias $aliasWalk = $snmp.GetTree($OID_IF_ALIAS) if ($aliasWalk) { foreach ($key in $aliasWalk.Keys) { if ($key -match '\.(\d+)$') { $ifIndex = $Matches[1] $alias = $aliasWalk[$key] if (-not [string]::IsNullOrWhiteSpace($alias)) { $ifAliasTable[$ifIndex] = $alias } } } } # Merge: for each IP in ARP table, look up port and description foreach ($ip in $arpTable.Keys) { $mac = $arpTable[$ip] $macNorm = ($mac -replace '[:\-\.]', '').ToUpperInvariant() $portIndex = $macPortTable[$macNorm] $portAlias = if ($portIndex) { $ifAliasTable[$portIndex.ToString()] } else { $null } $result[$ip] = [PSCustomObject]@{ MACAddress = $mac SwitchIP = $SwitchIP SwitchPort = $portIndex PortDescription = $portAlias } } } catch { Write-Warning "[$SwitchIP] SNMP walk failed: $($_.Exception.Message)" } finally { if ($snmp) { try { $snmp.Close() } catch { } try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($snmp) | Out-Null } catch { } $snmp = $null } } $result } |