Public/Get-VBOUIVendor.ps1
|
function Get-VBOUIVendor { <# .SYNOPSIS Look up the vendor organisation for a MAC address from the IEEE OUI database (Layer 11). .DESCRIPTION Downloads the IEEE OUI CSV from https://standards-oui.ieee.org/oui/oui.csv on the first call if the file is missing, and refreshes it if the file is older than 30 days. After download, the CSV is imported into a script-scope hashtable keyed by the 6-char uppercase OUI prefix. All subsequent lookups within the session hit the hashtable -- no file I/O per call. The vendor organisation name is also mapped to a device class hint via an ordered lookup table embedded in this function. The hints feed Resolve-VBDeviceClass Tier 13 as a low-confidence fallback. Download uses TLS 1.2 (forced on PS 5.1). The file is saved with UTF-8 BOM encoding. Prerequisite: $MACAddress must not be null/empty. No network prerequisite beyond the initial download (which happens silently). .PARAMETER MACAddress The MAC address to look up. Accepts any common separator format (00:1A:2B:3C:4D:5E, 00-1A-2B-3C-4D-5E, 001A.2B3C.4D5E, 001A2B3C4D5E). .PARAMETER Context Environment context from Get-VBEnrichmentContext. Provides OUIFilePath. If omitted, the default path $env:LOCALAPPDATA\VB.DNSEnrichment\oui.csv is used. .OUTPUTS [PSCustomObject] -- base layer result fields (IPAddress will be $null when called standalone without an IP) plus: Vendor [string] Raw IEEE organisation name VendorDeviceClass [string] Mapped class hint for Resolve-VBDeviceClass MACNormalised [string] 6-char uppercase OUI used for the lookup .EXAMPLE $ctx = Get-VBEnrichmentContext Get-VBOUIVendor -MACAddress '00:1A:2B:3C:4D:5E' -Context $ctx .EXAMPLE '00:1A:2B:3C:4D:5E' | Get-VBOUIVendor -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)] [AllowNull()] [AllowEmptyString()] [string]$MACAddress, [Parameter()] [PSCustomObject]$Context, # IPAddress is optional -- passed by orchestrator so result object has context [Parameter()] [string]$IPAddress = $null ) begin { $LAYER_NUM = 11 $LAYER_NAME = 'OUI' $ouiFilePath = if ($Context -and $Context.OUIFilePath) { $Context.OUIFilePath } else { Join-Path $env:LOCALAPPDATA 'VB.DNSEnrichment\oui.csv' } Write-Verbose "[OUI] Path: $ouiFilePath" # Vendor -> DeviceClass hint table (ordered -- first match wins) $vendorClassMap = [ordered]@{ 'Yealink' = 'IPPhone' 'Poly' = 'IPPhone' 'Polycom' = 'IPPhone' 'Grandstream' = 'IPPhone' 'Snom' = 'IPPhone' 'Cisco Systems' = 'NetworkDevice' 'Ubiquiti' = 'NetworkDevice' 'Aruba' = 'NetworkDevice' 'Juniper' = 'NetworkDevice' 'Fortinet' = 'NetworkDevice' 'Hikvision' = 'Camera' 'Dahua' = 'Camera' 'Axis' = 'Camera' 'Hanwha' = 'Camera' 'Hewlett Packard' = 'Workstation' 'HP Inc' = 'Workstation' 'Dell' = 'Workstation' 'Apple' = 'Workstation' 'Lenovo' = 'Workstation' 'APC' = 'UPS' 'Eaton' = 'UPS' 'Synology' = 'NAS' 'QNAP' = 'NAS' } # One-shot cache: load OUI table once per session if ($null -eq $Script:VBOUITable) { $Script:VBOUITable = Invoke-VBLoadOUITable -OUIFilePath $ouiFilePath } } process { $sw = [System.Diagnostics.Stopwatch]::StartNew() if ([string]::IsNullOrWhiteSpace($MACAddress)) { $sw.Stop() return New-VBLayerResult -IPAddress $IPAddress -Layer $LAYER_NUM -LayerName $LAYER_NAME ` -Status 'Skipped' -ExecutionMs $sw.ElapsedMilliseconds ` -SkipReason 'NoMACAvailable' ` -Impact 'No vendor lookup -- MAC address was not obtained from any prior layer' } try { $macNorm = ConvertTo-VBNormalisedMAC -MACAddress $MACAddress if ($null -eq $macNorm) { $sw.Stop() return New-VBLayerResult -IPAddress $IPAddress -Layer $LAYER_NUM -LayerName $LAYER_NAME ` -Status 'Failed' -ExecutionMs $sw.ElapsedMilliseconds ` -ErrorDetail "MAC '$MACAddress' could not be normalised -- unexpected format" } # OUI is the first 6 chars of the 12-char normalised MAC $oui = $macNorm.Substring(0, 6) $vendor = $Script:VBOUITable[$oui] if ([string]::IsNullOrWhiteSpace($vendor)) { $sw.Stop() return New-VBLayerResult -IPAddress $IPAddress -Layer $LAYER_NUM -LayerName $LAYER_NAME ` -Status 'NoResult' -ExecutionMs $sw.ElapsedMilliseconds ` -ExtraFields @{ Vendor = $null VendorDeviceClass = 'Unknown' MACNormalised = $oui } } # Map vendor name to device class hint $vendorClass = 'Unknown' foreach ($key in $vendorClassMap.Keys) { if ($vendor -match [regex]::Escape($key)) { $vendorClass = $vendorClassMap[$key] break } } $sw.Stop() New-VBLayerResult -IPAddress $IPAddress -Layer $LAYER_NUM -LayerName $LAYER_NAME ` -Status 'Success' -ExecutionMs $sw.ElapsedMilliseconds ` -ExtraFields @{ Vendor = $vendor VendorDeviceClass = $vendorClass MACNormalised = $oui } } catch { $sw.Stop() New-VBLayerResult -IPAddress $IPAddress -Layer $LAYER_NUM -LayerName $LAYER_NAME ` -Status 'Failed' -ExecutionMs $sw.ElapsedMilliseconds ` -ErrorDetail $_.Exception.Message } } } |