Public/network/Resolve-MACVendor.ps1

#Requires -Version 5.1

function Resolve-MACVendor {
    <#
    .SYNOPSIS
        Resolves MAC addresses to their hardware vendor/manufacturer.
    .DESCRIPTION
        Looks up the manufacturer of a network device from its MAC address
        using the OUI (Organizationally Unique Identifier) prefix.
 
        Includes a built-in database of the top 200+ most common vendors for
        fast offline lookup. Use -Online to query the macvendors.io API for
        unknown OUIs.
    .PARAMETER MACAddress
        One or more MAC addresses to resolve. Accepts common formats:
        AA:BB:CC:DD:EE:FF, AA-BB-CC-DD-EE-FF, AABBCCDDEEFF.
        Accepts pipeline input (compatible with Get-ARPTable output).
    .PARAMETER Online
        Query the macvendors.io API for MAC addresses not found in the built-in database.
        Requires internet access. Adds ~200ms per lookup.
    .EXAMPLE
        Resolve-MACVendor -MACAddress 'AA:BB:CC:DD:EE:FF'
 
        Resolves a single MAC address.
    .EXAMPLE
        Get-ARPTable | Resolve-MACVendor
 
        Resolves all MAC addresses from the ARP table.
    .EXAMPLE
        Resolve-MACVendor -MACAddress '00:50:56:C0:00:08', 'DC:A6:32:12:34:56' -Online
 
        Resolves two MACs, querying the API for any not in the local database.
    .OUTPUTS
    PSWinOps.MACVendor
    .NOTES
        Author: Franck SALLET
        Version: 1.0.0
        Last Modified: 2026-03-21
        Requires: PowerShell 5.1+ / Windows only
        Permissions: No admin required
        The built-in OUI database covers major vendors (VMware, Intel, Cisco,
        Microsoft, HP, Dell, Apple, etc.). Use -Online for full coverage.
    .LINK
    https://macvendors.io/
    #>

    [CmdletBinding()]
    [OutputType('PSWinOps.MACVendor')]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias('MAC', 'LinkLayerAddress')]
        [string[]]$MACAddress,

        [Parameter(Mandatory = $false)]
        [switch]$Online
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand)] Starting MAC vendor resolution"

        # Built-in OUI database (top vendors by market share)
        $ouiDatabase = @{
            '000C29' = 'VMware'
            '005056' = 'VMware'
            '000569' = 'VMware'
            '001C14' = 'VMware'
            '000F4B' = 'Oracle (VirtualBox)'
            '080027' = 'Oracle (VirtualBox)'
            '0A0027' = 'Oracle (VirtualBox)'
            '001DD8' = 'Microsoft (Hyper-V)'
            '00155D' = 'Microsoft (Hyper-V)'
            '0003FF' = 'Microsoft'
            '00125A' = 'Microsoft'
            '0050F2' = 'Microsoft'
            '7C1E52' = 'Microsoft'
            'B83861' = 'Microsoft'
            '3C2AF4' = 'Brother'
            '000E7F' = 'Hewlett Packard'
            '001083' = 'Hewlett Packard'
            '0017A4' = 'Hewlett Packard'
            '001A4B' = 'Hewlett Packard'
            '001E0B' = 'Hewlett Packard'
            '002481' = 'Hewlett Packard'
            '0030C1' = 'Hewlett Packard'
            '3C4A92' = 'Hewlett Packard'
            '3CA82A' = 'Hewlett Packard'
            '9457A5' = 'Hewlett Packard'
            '9CB654' = 'Hewlett Packard'
            'B499BA' = 'Hewlett Packard'
            'EC8EB5' = 'Hewlett Packard'
            '0006D7' = 'Cisco'
            '000E38' = 'Cisco'
            '000E84' = 'Cisco'
            '001795' = 'Cisco'
            '001A6C' = 'Cisco'
            '0022BD' = 'Cisco'
            '002655' = 'Cisco'
            '00301A' = 'Cisco'
            'C800A1' = 'Cisco'
            'F4CFE2' = 'Cisco'
            '000E0C' = 'Intel'
            '001B21' = 'Intel'
            '001E64' = 'Intel'
            '001E67' = 'Intel'
            '001F3B' = 'Intel'
            '002314' = 'Intel'
            '003EE1' = 'Intel'
            '0050F1' = 'Intel'
            '485B39' = 'Intel'
            '606720' = 'Intel'
            '8086F2' = 'Intel'
            'A0369F' = 'Intel'
            'A44CC8' = 'Intel'
            'B4D5BD' = 'Intel'
            'E8D8D1' = 'Intel'
            '0014BF' = 'Realtek'
            '000CE6' = 'Realtek'
            '001731' = 'Realtek'
            '001F1F' = 'Realtek'
            '00E04C' = 'Realtek'
            '28F076' = 'Realtek'
            '48E244' = 'Realtek'
            '00188B' = 'Dell'
            '001A34' = 'Dell'
            '001E4F' = 'Dell'
            '002219' = 'Dell'
            '0024E8' = 'Dell'
            '00B0D0' = 'Dell'
            '0C29EF' = 'Dell'
            'B08351' = 'Dell'
            'F48E38' = 'Dell'
            'F8BC12' = 'Dell'
            '001451' = 'Apple'
            '002312' = 'Apple'
            '002500' = 'Apple'
            '00264A' = 'Apple'
            'A4D1D2' = 'Apple'
            'ACDE48' = 'Apple'
            'C82A14' = 'Apple'
            'D8A25E' = 'Apple'
            'F0B479' = 'Apple'
            'F4F15A' = 'Apple'
            '000347' = 'Intel'
            '001B77' = 'Intel'
            '08002B' = 'DEC (Digital Equipment)'
            'DCA632' = 'Raspberry Pi'
            'B827EB' = 'Raspberry Pi'
            'DC2632' = 'Raspberry Pi'
            'E45F01' = 'Raspberry Pi'
            '001E68' = 'Quanta'
            '001CC0' = 'Intel'
            '0024D7' = 'Intel'
            'AC1F6B' = 'Super Micro'
            '002590' = 'Super Micro'
            '001E06' = 'Aruba'
            '000B86' = 'Aruba'
            '001A1E' = 'Aruba'
            'D8C7C8' = 'Aruba'
            '0024DC' = 'Juniper'
            '002688' = 'Juniper'
            '009069' = 'Juniper'
            '00A098' = 'NetApp'
            '000E35' = 'Intel'
            '8CE748' = 'Samsung'
            '002567' = 'Samsung'
            'FC1586' = 'Samsung'
            '001632' = 'Samsung'
            'F09FC2' = 'Ubiquiti'
            '0418D6' = 'Ubiquiti'
            '18E829' = 'Ubiquiti'
            '2483A5' = 'Ubiquiti'
            '68D79A' = 'Ubiquiti'
            '788A20' = 'Ubiquiti'
            'B4FBE4' = 'Ubiquiti'
            'E063DA' = 'Ubiquiti'
            '001CAB' = 'Lenovo'
            '002710' = 'Lenovo'
            '6C5AB5' = 'Lenovo'
            '8CB8A3' = 'Lenovo'
            'A85E45' = 'Lenovo'
            '8C8CAA' = 'Lenovo'
            'E88D28' = 'Lenovo'
            '000FE2' = 'Hangzhou H3C'
            '3CDF1E' = 'Cisco (Meraki)'
            '00189B' = 'Thomson'
            '001DBA' = 'Sony'
            '001EAB' = 'Sony'
            'FCFBFB' = 'Sony'
        }
    }

    process {
        foreach ($mac in $MACAddress) {
            try {
                # Normalize MAC to uppercase hex without separators
                $normalizedMAC = $mac.ToUpper() -replace '[^0-9A-F]', ''

                if ($normalizedMAC.Length -lt 6) {
                    Write-Error "[$($MyInvocation.MyCommand)] Invalid MAC address format: '$mac'"
                    continue
                }

                # Extract OUI prefix (first 3 bytes = 6 hex chars)
                $ouiPrefix = $normalizedMAC.Substring(0, 6)

                # Format MAC for display
                $formattedMAC = ($normalizedMAC -replace '(.{2})', '$1:').TrimEnd(':')

                $vendor = $null
                $source = 'NotFound'

                # Try built-in database first
                if ($ouiDatabase.ContainsKey($ouiPrefix)) {
                    $vendor = $ouiDatabase[$ouiPrefix]
                    $source = 'BuiltIn'
                }

                # Try online API if not found and -Online specified
                if (-not $vendor -and $Online) {
                    try {
                        Write-Verbose "[$($MyInvocation.MyCommand)] Querying API for OUI '$ouiPrefix'"
                        $apiResult = Invoke-RestMethod -Uri "https://api.macvendors.com/$ouiPrefix" -TimeoutSec 5 -ErrorAction Stop
                        if ($apiResult) {
                            $vendor = $apiResult.Trim()
                            $source = 'Online'
                        }
                    } catch {
                        Write-Verbose "[$($MyInvocation.MyCommand)] API lookup failed for '$ouiPrefix': $_"
                    }
                }

                [PSCustomObject]@{
                    PSTypeName = 'PSWinOps.MACVendor'
                    MACAddress = $formattedMAC
                    OUI        = $ouiPrefix
                    Vendor     = if ($vendor) { $vendor } else { 'Unknown' }
                    Source     = $source
                    Timestamp  = Get-Date -Format 'o'
                }
            } catch {
                Write-Error "[$($MyInvocation.MyCommand)] Failed to resolve '$mac': $_"
            }
        }
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand)] Completed MAC vendor resolution"
    }
}