Public/Resolve-VBDeviceClass.ps1
|
function Resolve-VBDeviceClass { <# .SYNOPSIS Classify an enriched IP address into a device class from collected signal data. .DESCRIPTION Pure classification logic -- never calls any probe function. Takes the merged signals from all layer outputs (OSClass, open ports, HTTP/SNMP/RTSP/mDNS strings, OUI vendor) and returns a DeviceClass, Confidence, and DeviceClassSource. Classification runs through a tiered switch in priority order: Tier 1 -- AD OSClass (authoritative -- domain-joined machines) Tier 2 -- Cameras (RTSP port 554, banner, or vendor string) Tier 3 -- VoIP (SIP ports 5060/5061, or vendor string) Tier 4 -- Printers (JetDirect 9100, IPP 631, or vendor string) Tier 5 -- Scanners (mDNS _scanner service type) Tier 6 -- VMware (ESXi ports 902/9443, or banner) Tier 7 -- NAS (vendor/banner string match) Tier 8 -- UPS/PDU (vendor/banner string match) Tier 9 -- Network (Cisco/Ubiquiti/Aruba etc.) Tier 10 -- iOS (port 62078) Tier 11 -- IoT/MQTT (port 1883) Tier 12 -- Windows fallback (RDP+RPC = Workstation, WinRM+SMB = Server) Tier 13 -- OUI-only (lowest-signal fallback) default -- Unknown This function does NOT enforce the skip-if-resolved gate -- that is the orchestrator's responsibility. .PARAMETER OSClass From Get-VBADComputer. 'DomainController', 'Server', or 'Workstation'. .PARAMETER OpenPorts Comma-separated port list from Get-VBTCPFingerprint (e.g. '80,443,9100'). .PARAMETER HTTPTitle Page title from Get-VBHTTPBanner. .PARAMETER HTTPServer Server header from Get-VBHTTPBanner. .PARAMETER SNMPDescr sysDescr OID value from Get-VBSNMPIdentity. .PARAMETER RTSPBanner Banner from Get-VBRTSPBanner. .PARAMETER OUIVendor Organization name from Get-VBOUIVendor. .PARAMETER VendorDeviceClass Pre-mapped class hint from Get-VBOUIVendor lookup table. .PARAMETER MDNSServiceType Service type string from Get-VBmDNSRecord (e.g. '_scanner._tcp'). .OUTPUTS [PSCustomObject] DeviceClass [string] Confidence [string] High | Medium | Low | None DeviceClassSource [string] comma-separated list of signals that matched .EXAMPLE Resolve-VBDeviceClass -OSClass 'Server' -OpenPorts '135,445,3389,5985' .EXAMPLE Resolve-VBDeviceClass -OpenPorts '9100,631' -OUIVendor 'HP Inc.' .NOTES Version: 1.0.0 MinPSVersion: 5.1 Author: VB ChangeLog: 1.0.0 -- 2026-05-11 -- Initial release #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter()] [string]$OSClass, [Parameter()] [string]$OpenPorts, [Parameter()] [string]$HTTPTitle, [Parameter()] [string]$HTTPServer, [Parameter()] [string]$SNMPDescr, [Parameter()] [string]$RTSPBanner, [Parameter()] [string]$OUIVendor, [Parameter()] [string]$VendorDeviceClass, [Parameter()] [string]$MDNSServiceType ) # Combine free-text signals into one lowercase string for regex matching $combined = ( "$HTTPTitle $HTTPServer $SNMPDescr $RTSPBanner $OUIVendor" ).ToLower().Trim() # Parse open ports into an int array $ports = @() if (-not [string]::IsNullOrWhiteSpace($OpenPorts)) { $ports = @( $OpenPorts -split ',' | ForEach-Object { $t = $_.Trim() if ($t -match '^\d+$') { [int]$t } } ) } $sources = New-Object System.Collections.Generic.List[string] $class = switch ($true) { # Tier 1 -- AD OSClass is authoritative ($OSClass -eq 'DomainController') { $sources.Add('OSClass') 'DomainController' break } ($OSClass -eq 'Server') { $sources.Add('OSClass') 'Server' break } ($OSClass -eq 'Workstation') { $sources.Add('OSClass') 'Workstation' break } # Tier 2 -- Cameras (port 554 alone is insufficient -- require banner or vendor match too) ( ($ports -contains 554 -and (-not [string]::IsNullOrWhiteSpace($RTSPBanner) -or $combined -match 'hikvision|dahua|axis|hanwha|bosch.*security|milestone|reolink|amcrest') ) -or $RTSPBanner -match 'RTSP' -or $combined -match 'hikvision|dahua|axis|hanwha|bosch.*security|milestone|reolink|amcrest' ) { $sources.Add('RTSPOrVendor') 'Camera' break } # Tier 3 -- VoIP / IP phones ( $ports -contains 5060 -or $ports -contains 5061 -or $combined -match 'yealink|polycom|grandstream|snom|sip.*phone|voip|cisco.*phone|cisco ip' ) { $sources.Add('SIPOrVendor') 'IPPhone' break } # Tier 4 -- Printers ( $ports -contains 9100 -or $ports -contains 631 -or $ports -contains 515 -or $MDNSServiceType -match '_ipp\._tcp|_pdl-datastream\._tcp|_ipps\._tcp' -or $combined -match 'jetdirect|kyocera|ricoh|xerox|laserjet|bizhub|taskalfa|workcentre|brother|lexmark|konica|sharp.*mx|develop.*ineo' ) { $sources.Add('PrinterPortOrVendor') 'Printer' break } # Tier 5 -- Scanners (mDNS service type is the primary signal) ( $MDNSServiceType -match '_scanner' -or $combined -match '\bscanner\b' ) { $sources.Add('mDNSScanner') 'Scanner' break } # Tier 6 -- VMware ESXi / virtual hosts ( $ports -contains 902 -or $ports -contains 9443 -or $combined -match 'esxi|vmware|vsphere|vcenter' ) { $sources.Add('ESXiPortOrBanner') 'VirtualHost' break } # Tier 7 -- NAS ( $combined -match 'synology|qnap|nas|diskstation|truenas|freenas|readynas|drobo' ) { $sources.Add('NASBanner') 'NAS' break } # Tier 8 -- UPS / PDU ( $combined -match '\bapc\b|eaton|\bups\b|\bpdu\b|powerware|tripplite|cyberpower' ) { $sources.Add('UPSBanner') 'UPS' break } # Tier 9 -- Network infrastructure ( $combined -match 'cisco|ubiquiti|aruba|juniper|fortinet|mikrotik|unifi|palo.*alto|sonicwall|meraki|netgear|draytek' -or $combined -match '\bswitch\b|\brouter\b|\bfirewall\b|\baccess.point\b|\bwap\b' ) { $sources.Add('NetworkVendorOrBanner') 'NetworkDevice' break } # Tier 10 -- iOS (iTunes sync port) ($ports -contains 62078) { $sources.Add('iOSPort') 'Mobile' break } # Tier 11 -- IoT / MQTT broker ($ports -contains 1883) { $sources.Add('MQTTPort') 'IoT' break } # Tier 12 -- Windows fingerprint fallback (no AD) ($ports -contains 3389 -and $ports -contains 135) { $sources.Add('WindowsPorts') 'Workstation' break } ($ports -contains 5985 -and $ports -contains 445) { $sources.Add('WindowsPorts') 'Server' break } # Tier 13 -- OUI vendor hint (lowest signal -- no ports, no banners) ( -not [string]::IsNullOrWhiteSpace($VendorDeviceClass) -and $VendorDeviceClass -ne 'Unknown' ) { $sources.Add('OUIVendor') $VendorDeviceClass break } default { 'Unknown' } } $confidence = switch ($class) { 'DomainController' { 'High' } 'Server' { 'High' } 'Workstation' { 'High' } 'IPPhone' { 'High' } 'Printer' { 'High' } 'Camera' { if (-not [string]::IsNullOrWhiteSpace($RTSPBanner) -or $combined -match 'hikvision|dahua|axis') { 'High' } elseif ($ports -contains 554) { 'Medium' } else { 'Low' } } 'Unknown' { 'None' } default { 'Medium' } } [PSCustomObject]@{ DeviceClass = $class Confidence = $confidence DeviceClassSource = ($sources -join ',') } } |