Public/Get-VBARPEntry.ps1
|
function Get-VBARPEntry { <# .SYNOPSIS Look up a MAC address for an IP via the local ARP cache (Layer 4). .DESCRIPTION On the first call within a session, parses the output of 'arp -a' into a script-scope hashtable keyed by IP address. All subsequent calls perform a hashtable lookup with zero I/O cost. If the IP is not found in the initial ARP table, the function optionally sends a single ICMP ping (200 ms timeout) to populate the OS ARP table, then re-reads the cache for that IP only. No network prerequisite -- arp.exe is always available on Windows. .PARAMETER IPAddress The RFC1918 / CGNAT / link-local IP address to look up. .PARAMETER Context Environment context object from Get-VBEnrichmentContext. Accepted for pipeline consistency; no flags required by this layer. .OUTPUTS [PSCustomObject] -- base layer result fields plus: MACAddress [string] MACNormalised [string] ARPType [string] static | dynamic PingedToPopulate [bool] .EXAMPLE $ctx = Get-VBEnrichmentContext Get-VBARPEntry -IPAddress '192.168.1.45' -Context $ctx .EXAMPLE '192.168.1.45','192.168.1.46' | Get-VBARPEntry -Context $ctx .NOTES Version: 1.0.0 MinPSVersion: 5.1 Author: VB ChangeLog: 1.0.0 -- 2026-05-11 -- Initial release #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string]$IPAddress, [Parameter()] [PSCustomObject]$Context ) begin { $LAYER_NUM = 4 $LAYER_NAME = 'ARP' $CacheTTLMinutes = 60 if ($null -ne $Script:VBArpCache) { $ageMin = ((Get-Date) - $Script:VBArpCacheBuiltAt).TotalMinutes if ($ageMin -gt $CacheTTLMinutes) { Write-Verbose "[$LAYER_NAME] ARP cache is $([int]$ageMin) min old (TTL $CacheTTLMinutes min) -- rebuilding" $Script:VBArpCache = $null } } if ($null -eq $Script:VBArpCache) { $Script:VBArpCache = Get-VBARPTable $Script:VBArpCacheBuiltAt = Get-Date Write-Verbose "[$LAYER_NAME] ARP cache built: $($Script:VBArpCache.Count) entries" } } process { $sw = [System.Diagnostics.Stopwatch]::StartNew() try { $entry = $Script:VBArpCache[$IPAddress] $pinged = $false if ($null -eq $entry) { # Ping to populate OS ARP table then re-check this one IP $pinged = $true Write-Verbose "[$LAYER_NAME] $IPAddress not in ARP cache -- pinging to populate" $null = Test-Connection -ComputerName $IPAddress -Count 1 -ErrorAction SilentlyContinue # Re-read only arp -a for the specific IP (fast) $refreshed = Get-VBARPTable -IPFilter $IPAddress if ($refreshed.Count -gt 0) { $entry = $refreshed[$IPAddress] # Merge into session cache if ($null -ne $entry) { $Script:VBArpCache[$IPAddress] = $entry } } } if ($null -eq $entry) { $sw.Stop() return New-VBLayerResult -IPAddress $IPAddress -Layer $LAYER_NUM -LayerName $LAYER_NAME ` -Status 'NoResult' -ExecutionMs $sw.ElapsedMilliseconds } $macNormalised = ConvertTo-VBNormalisedMAC -MACAddress $entry.MAC $sw.Stop() New-VBLayerResult -IPAddress $IPAddress -Layer $LAYER_NUM -LayerName $LAYER_NAME ` -Status 'Success' -ExecutionMs $sw.ElapsedMilliseconds ` -ExtraFields @{ MACAddress = $entry.MAC MACNormalised = $macNormalised ARPType = $entry.Type PingedToPopulate = $pinged } } catch { $sw.Stop() New-VBLayerResult -IPAddress $IPAddress -Layer $LAYER_NUM -LayerName $LAYER_NAME ` -Status 'Failed' -ExecutionMs $sw.ElapsedMilliseconds ` -ErrorDetail $_.Exception.Message } } } |