Function convert-netbiosType([byte]$val) { #note netbios type codes are usually in decimal, but .net likes to deal with bytes #as integers. $myval = [int]$val switch($myval) { 0 { return "Workstation" } 1 { return "Messenger service" } 3 { return "Messenger" } 6 { return "RAS" } 32 { return "File Service" } 27 { return "Domain Master Browser" } 28 { return "Domain Controller" } 29 { return "Master Browser" } 30 { return "Browser election" } 31 { return "NetDDE" } 33 { return "RAS Client" } 34 { return "Exchange MS mail connector" } 35 { return "Exchange Store" } 36 { return "Exchange Directory" } 48 { return "Modem sharing service Server"} 49 { return "Modem sharing service Client"} 67 { return "SMS client remote control" } 68 { return "SMS client remote transfer" } 135 { return "Exchange MTA" } default { return "unk" } } } function get-netbios-name ([string]$ip) { #Function: Get netbios name of the remote machine by IP address provided # result: Error = $null, positive result is hashtable of names if (-not ($ip -match "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")) { write-error "The Ip address provided: $ip is not a valid IPv4 address format" return $null } #ping first for reachability check $po = New-Object net.NetworkInformation.PingOptions $po.set_ttl(64) $po.set_dontfragment($true) #alexander ping [Byte[]] $pingbytes = (65,72,79,89) $ping = new-object Net.NetworkInformation.Ping $pingres = $ping.send($ip, 1000, $pingbytes, $po) if ($pingres.status -eq "Success") { #netbios name query NBTNS $port=137 $ipEP = new-object System.Net.IPEndPoint ([system.net.IPAddress]::parse($ip),$port) $udpconn = new-Object System.Net.Sockets.UdpClient [byte[]] $sendbytes = (0xf4,0x53,00,00,00,01,00,00,00,00,00,00,0x20,0x43,0x4b,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41 ,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,00,00,0x21,00,01) $udpconn.client.receivetimeout=1000 $bytesSent = $udpconn.Send($sendbytes,50,$ipEP) $rcvbytes = $udpconn.Receive([ref]$ipEP) if ($? -eq $false -or $rcvbytes.length -lt 63) { write-error "System is not responding to netbios traffic on port 137, system is not a windows machine, or other error has occurred." return $null } else { [array]$nbnames = $null #nbtns query results have a number of returned records field at byte #56 of the returned #udp payload. Read this value to find how many records we have $startptr = 56 $numresults = [int]$rcvbytes[$startptr] $startptr++ $namereclen = 18 #loop through the number of results and get the names + data # NETBIOS result = 15 byte of name (padded if shorted 0x20) # 1 byte of type # 2 byte of flags for ($i = 0; $i -lt $numresults; $i++) { $nbname = new-object PSObject $tempname = "" #read the 15 byte name and convert to human readable string for ($j = 0; $j -lt $namereclen -3; $j++) { $tempname += [char]$rcvbytes[$startptr + ($i * $namereclen) + $j] } add-member -input $nbname NoteProperty NetbiosName $tempname $rectype = convert-netbiosType $rcvbytes[$startptr + ($i * $namereclen) + 15] add-member -input $nbname NoteProperty RecordType $rectype if (($rcvbytes[$startptr + ($i * $namereclen) + 16] -band 128) -eq 128 ) { #in the flags field, only the high order byte of the 2 is used #the left most bit is the Group name flag which can be used for domain #name type identification to differentiate the 0x00 type names $groupflag = 1 } else { $groupflag = 0 } add-member -input $nbname NoteProperty IsGroupType $groupflag $nbnames += $nbname } return $nbnames } } else { write-error "System not pinging: $ip" #prompt for another ip to be inputted? return $null } } $ip = args[0] if ($ip -eq $null -or $ip -eq "") { write-host "You need to provide an ip address to check" exit } get-netbios-name $ip
For those that use nbtstat, you will also know that it returns the MAC address, however not in the best or most efficient way. Since nbtstat tries to use every network adapter on the machine, if you have multiple nics and virtualization nics, then it is slow. The benefit of the powershell method above is that you only send out from one NIC. To get the MAC address, the code can be modified to pull it out of the received packets. MAC address is the last useful 6 bytes of the packet (which may be padded with extra 0's at the end). So you can use this to grab those bytes and format it to a mac address string.
$mac = (0,0,0,0,0,0) $j = 5 for ($i = $rcvbytes.length - 1; $i -gt 0; $i--) { if ($rcvbytes[$i] -ne 0x0) { $mac[$j] = $rcvbytes[$i] $j-- if ($j -eq -1) { $i = -1 } } } $macstring = "" foreach ($byte in $mac) { $macstring += ("{0:X2}" -f $byte) + "-" } new-object psobject -property @{ IP = $ip MacAddress = $macstring.trim("-") }
Excellent code. Heads up there's an error in getting the mac address.
ReplyDeleteforeach (byte in $mac) {
should read:
foreach ($byte in $mac) {
Thanks heaps for sharing!!!
another small little bug in the get mac address.
ReplyDeleteMacAddress = $macstring.trim("=")
should read:
MacAddress = $macstring.trim("-")
Thanks for catching that Viriio, I have updated it with the corrections.
ReplyDelete