├── .readme ├── ClearARP.png ├── ContextMenuExport.png ├── CopyItemToClip.png ├── DoubleClickPopup.png ├── HTMLexample-export.png ├── IPScanner.png ├── MonitorMode.png ├── PortScan.png ├── ScanContextSubnet.png └── ToggleModes.png ├── IPScanner.cmd ├── LICENSE ├── README.md ├── cmd-wmic-version └── IPScanner.cmd └── console-crossplatform └── IPScanner.ps1 /.readme/ClearARP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/illsk1lls/IPScanner/c408fda7b4569ccbb6a80f820f07bd4ab7abf39f/.readme/ClearARP.png -------------------------------------------------------------------------------- /.readme/ContextMenuExport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/illsk1lls/IPScanner/c408fda7b4569ccbb6a80f820f07bd4ab7abf39f/.readme/ContextMenuExport.png -------------------------------------------------------------------------------- /.readme/CopyItemToClip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/illsk1lls/IPScanner/c408fda7b4569ccbb6a80f820f07bd4ab7abf39f/.readme/CopyItemToClip.png -------------------------------------------------------------------------------- /.readme/DoubleClickPopup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/illsk1lls/IPScanner/c408fda7b4569ccbb6a80f820f07bd4ab7abf39f/.readme/DoubleClickPopup.png -------------------------------------------------------------------------------- /.readme/HTMLexample-export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/illsk1lls/IPScanner/c408fda7b4569ccbb6a80f820f07bd4ab7abf39f/.readme/HTMLexample-export.png -------------------------------------------------------------------------------- /.readme/IPScanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/illsk1lls/IPScanner/c408fda7b4569ccbb6a80f820f07bd4ab7abf39f/.readme/IPScanner.png -------------------------------------------------------------------------------- /.readme/MonitorMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/illsk1lls/IPScanner/c408fda7b4569ccbb6a80f820f07bd4ab7abf39f/.readme/MonitorMode.png -------------------------------------------------------------------------------- /.readme/PortScan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/illsk1lls/IPScanner/c408fda7b4569ccbb6a80f820f07bd4ab7abf39f/.readme/PortScan.png -------------------------------------------------------------------------------- /.readme/ScanContextSubnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/illsk1lls/IPScanner/c408fda7b4569ccbb6a80f820f07bd4ab7abf39f/.readme/ScanContextSubnet.png -------------------------------------------------------------------------------- /.readme/ToggleModes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/illsk1lls/IPScanner/c408fda7b4569ccbb6a80f820f07bd4ab7abf39f/.readme/ToggleModes.png -------------------------------------------------------------------------------- /IPScanner.cmd: -------------------------------------------------------------------------------- 1 | <# :: Hybrid CMD / Powershell Launcher - Rename file to .CMD to Autolaunch with console settings (Double-Click) - Rename to .PS1 to run as Powershell script without console settings 2 | @ECHO OFF 3 | SET "0=%~f0"&SET "LEGACY={B23D10C0-E52E-411E-9D5B-C09FDF709C7D}"&SET "LETWIN={00000000-0000-0000-0000-000000000000}"&SET "TERMINAL={2EACA947-7F5F-4CFA-BA87-8F7FBEEFBE69}"&SET "TERMINAL2={E12CFF52-A866-4C77-9A90-F570A7AA2C6B}" 4 | POWERSHELL -nop -c "Get-WmiObject -Class Win32_OperatingSystem | Select -ExpandProperty Caption | Find 'Windows 11'">nul 5 | IF ERRORLEVEL 0 ( 6 | SET isEleven=1 7 | >nul 2>&1 REG QUERY "HKCU\Console\%%%%Startup" /v DelegationConsole 8 | IF ERRORLEVEL 1 ( 9 | REG ADD "HKCU\Console\%%%%Startup" /v DelegationConsole /t REG_SZ /d "%LETWIN%" /f>nul 10 | REG ADD "HKCU\Console\%%%%Startup" /v DelegationTerminal /t REG_SZ /d "%LETWIN%" /f>nul 11 | ) 12 | FOR /F "usebackq tokens=3" %%# IN (`REG QUERY "HKCU\Console\%%%%Startup" /v DelegationConsole 2^>nul`) DO ( 13 | IF NOT "%%#"=="%LEGACY%" ( 14 | SET "DEFAULTCONSOLE=%%#" 15 | REG ADD "HKCU\Console\%%%%Startup" /v DelegationConsole /t REG_SZ /d "%LEGACY%" /f>nul 16 | REG ADD "HKCU\Console\%%%%Startup" /v DelegationTerminal /t REG_SZ /d "%LEGACY%" /f>nul 17 | ) 18 | ) 19 | ) 20 | START /MIN "" POWERSHELL -nop -c "iex ([io.file]::ReadAllText('%~f0'))">nul 21 | IF "%isEleven%"=="1" ( 22 | IF DEFINED DEFAULTCONSOLE ( 23 | IF "%DEFAULTCONSOLE%"=="%TERMINAL%" ( 24 | REG ADD "HKCU\Console\%%%%Startup" /v DelegationConsole /t REG_SZ /d "%TERMINAL%" /f>nul 25 | REG ADD "HKCU\Console\%%%%Startup" /v DelegationTerminal /t REG_SZ /d "%TERMINAL2%" /f>nul 26 | ) ELSE ( 27 | REG ADD "HKCU\Console\%%%%Startup" /v DelegationConsole /t REG_SZ /d "%DEFAULTCONSOLE%" /f>nul 28 | REG ADD "HKCU\Console\%%%%Startup" /v DelegationTerminal /t REG_SZ /d "%DEFAULTCONSOLE%" /f>nul 29 | ) 30 | ) 31 | ) 32 | EXIT 33 | #>if($env:0){$PSCommandPath="$env:0"} 34 | ###POWERSHELL BELOW THIS LINE### 35 | 36 | # Hide Console - Show GUI Only - Only works for Legacy console 37 | Add-Type -MemberDefinition '[DllImport("User32.dll")]public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);' -Namespace Win32 -Name Functions 38 | $closeConsoleUseGUI=[Win32.Functions]::ShowWindow((Get-Process -Id $PID).MainWindowHandle,0) 39 | 40 | # Allow Single Instance Only 41 | $AppId = 'Simple IP Scanner' 42 | $singleInstance = $false 43 | $script:SingleInstanceEvent = New-Object Threading.EventWaitHandle $true,([Threading.EventResetMode]::ManualReset),"Global\$AppId",([ref] $singleInstance) 44 | if (-not $singleInstance){ 45 | $shell = New-Object -ComObject Wscript.Shell 46 | $shell.Popup("$AppId is already running!",0,'ERROR:',0x0) | Out-Null 47 | Exit 48 | } 49 | 50 | # Check if .NET Framework version is at least 3.0, which is required for WPF applications 51 | $frameworks = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse | Get-ItemProperty -Name Version -EA 0 | Where-Object { $_.PSChildName -Match '^(?!S)\p{L}'} | Select-Object -ExpandProperty Version 52 | $highestVersion = $frameworks | ForEach-Object { [version]$_ } | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum 53 | if ($highestVersion -lt [version]'3.0') { 54 | $dotnetchecker = New-Object -ComObject Wscript.Shell 55 | $dotnetchecker.Popup("dotNET 3.0 or higher is required!",0,'ERROR:',0x0) | Out-Null 56 | Exit 57 | } 58 | 59 | # GUI Main Dispatcher 60 | function Update-uiMain(){ 61 | $Main.Dispatcher.Invoke([Windows.Threading.DispatcherPriority]::Background, [action]{}) 62 | } 63 | 64 | function Update-Progress { 65 | param ($value, $text) 66 | $Progress.Value = $value 67 | $BarText.Text = $text 68 | Update-uiMain 69 | } 70 | 71 | # Find gateway 72 | $route = Get-NetRoute -DestinationPrefix 0.0.0.0/0 | Select-Object -First 1 73 | $global:gateway = $route.NextHop 74 | $gatewayParts = $global:gateway -split '\.' 75 | $global:gatewayPrefix = (($gatewayParts[0..2] -join '.') + '.') 76 | 77 | # Store the original gateway prefix for reset functionality 78 | $originalGatewayPrefix = $global:gatewayPrefix 79 | 80 | # Initialize RunspacePool 81 | $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() 82 | $RunspacePool = [runspacefactory]::CreateRunspacePool(1, [System.Environment]::ProcessorCount, $SessionState, $Host) 83 | $RunspacePool.Open() 84 | 85 | # Get Host Info 86 | function Get-HostInfo { 87 | param( 88 | [string]$gateway, 89 | [string]$gatewayPrefix, 90 | [string]$originalGatewayPrefix 91 | ) 92 | $getHostInfoScriptBlock = { 93 | param( 94 | [string]$gateway, 95 | [string]$gatewayPrefix, 96 | [string]$originalGatewayPrefix 97 | ) 98 | # Get Hostname 99 | $hostName = [System.Net.Dns]::GetHostName() 100 | 101 | # Check internet connection and get external IP 102 | $ProgressPreference = 'SilentlyContinue' 103 | try { 104 | $ncsiCheck = Invoke-RestMethod "http://www.msftncsi.com/ncsi.txt" 105 | if ($ncsiCheck -eq "Microsoft NCSI") { 106 | $externalIP = Invoke-RestMethod "http://ifconfig.me/ip" 107 | } else { 108 | $externalIP = "No Internet or Redirection" 109 | } 110 | } catch { 111 | $externalIP = "No Internet or Error" 112 | } 113 | $ProgressPreference = 'Continue' 114 | 115 | # Use the passed gateway and gatewayPrefix 116 | $internalIP = (Get-NetIPAddress | Where-Object { 117 | $_.AddressFamily -eq 'IPv4' -and 118 | $_.InterfaceAlias -ne 'Loopback Pseudo-Interface 1' -and 119 | $_.IPAddress -like "$originalGatewayPrefix*" 120 | }).IPAddress 121 | 122 | # Get current adapter 123 | $adapter = (Get-NetIPAddress -AddressFamily IPv4 | Where-Object { 124 | $_.InterfaceAlias -match 'Ethernet|Wi-Fi' -and 125 | $_.IPAddress -like "$originalGatewayPrefix*" 126 | }).InterfaceAlias 127 | 128 | # Get MAC address 129 | $myMac = (Get-NetAdapter -Name $adapter).MacAddress -replace '-', ':' 130 | 131 | # Get domain 132 | $domain = (Get-CimInstance -ClassName Win32_ComputerSystem).Domain 133 | 134 | # Init ARP cache data 135 | $arpInit = Get-NetNeighbor | Where-Object {($_.State -eq "Reachable" -or $_.State -eq "Stale") -and ($_.IPAddress -like "$gatewayPrefix*") -and -not $_.IPAddress.Contains(':')} | Select-Object -Property IPAddress, LinkLayerAddress 136 | 137 | # Mark empty as unknown 138 | $variables = @('hostName', 'externalIP', 'internalIP', 'gateway', 'domain') 139 | foreach ($item in $variables) { 140 | if (-not (Get-Variable -Name $item -ValueOnly)) { 141 | Set-Variable -Name $item -Value 'Unknown' 142 | } 143 | } 144 | 145 | return @{ 146 | 'hostName' = $hostName; 147 | 'externalIP' = $externalIP; 148 | 'internalIP' = $internalIP; 149 | 'gateway' = $gateway; 150 | 'gatewayPrefix' = $gatewayPrefix; 151 | 'adapter' = $adapter; 152 | 'myMac' = $myMac; 153 | 'domain' = $domain; 154 | 'arpInit' = $arpInit; 155 | } 156 | } 157 | 158 | $getHostInfoThread = [powershell]::Create().AddScript($getHostInfoScriptBlock) 159 | $getHostInfoThread.AddArgument($global:gateway) 160 | $getHostInfoThread.AddArgument($global:gatewayPrefix) 161 | $getHostInfoThread.AddArgument($originalGatewayPrefix) 162 | $getHostInfoThread.RunspacePool = $RunspacePool 163 | $getHostInfoAsync = $getHostInfoThread.BeginInvoke() 164 | $getHostInfoAsync.AsyncWaitHandle.WaitOne() 165 | $hostInfoResults = $getHostInfoThread.EndInvoke($getHostInfoAsync) 166 | $global:hostName = $hostInfoResults.hostName 167 | $global:externalIP = $hostInfoResults.externalIP 168 | $global:internalIP = $hostInfoResults.internalIP 169 | $global:gateway = $hostInfoResults.gateway 170 | $global:gatewayPrefix = $hostInfoResults.gatewayPrefix 171 | $global:adapter = $hostInfoResults.adapter 172 | $global:myMac = $hostInfoResults.myMac 173 | $global:domain = $hostInfoResults.domain 174 | $global:arpInit = $hostInfoResults.arpInit 175 | Update-Progress 0 'Scanning' 176 | $getHostInfoThread.Dispose() 177 | } 178 | 179 | # Send packets across subnet (progress values adjusted for proper display) 180 | function Scan-Subnet { 181 | $progressCounter = 0 182 | 183 | 1..254 | ForEach-Object { 184 | $progressCounter++ 185 | $percentComplete = [math]::Min([math]::Round(($progressCounter / 240) * 100), 100) 186 | Test-Connection -ComputerName "$global:gatewayPrefix$_" -Count 1 -AsJob | Out-Null 187 | if($percentComplete -ge 100){ 188 | Update-Progress -value $percentComplete -text "Listening" 189 | } else { 190 | Update-Progress -value $percentComplete -text "Sending Packets" 191 | } 192 | } 193 | Update-Progress -value 100 -text "Listening" 194 | Get-Job | Wait-Job -ErrorAction Stop | Out-Null 195 | $results = Get-Job | Receive-Job -ErrorAction Stop 196 | $global:successfulPings = @($results | Where-Object { $_.StatusCode -eq 0 } | Select-Object -ExpandProperty Address) 197 | Get-Job | Remove-Job -Force 198 | } 199 | 200 | # Create peer list 201 | function List-Machines { 202 | Update-Progress 0 'Identifying Devices' 203 | 204 | # Convert IP Addresses from string to int by each section 205 | $arpOutput = $arpInit | Where-Object { $_.IPAddress -match "^\d+\.\d+\.\d+\.\d+$" } | Sort-Object -Property { $ip = $_.IPAddress; [version]($ip) } 206 | 207 | $self = 0 208 | $myLastOctet = [int]($internalIP -split '\.')[-1] 209 | 210 | # Get Vendor via Mac (thanks to u/mprz) 211 | $ProgressPreference = 'SilentlyContinue' 212 | $tryMyVendor = (irm "https://www.macvendorlookup.com/api/v2/$($myMac.Replace(':','').Substring(0,6))" -Method Get).Company 213 | $ProgressPreference = 'Continue' 214 | $myVendor = if($tryMyVendor){$tryMyVendor.substring(0, [System.Math]::Min(35, $tryMyVendor.Length))} else {'Unable to Identify'} 215 | 216 | # Cycle through ARP table to populate initial ListView data and start async lookups 217 | $totalItems = ($arpOutput.Count - 1) 218 | 219 | # First, add all known ARP entries and hostnames 220 | foreach ($line in $arpOutput) { 221 | $ip = $line.IPAddress 222 | $mac = $line.LinkLayerAddress.Replace('-',':') 223 | $quickNameLookup = ((Resolve-DnsName -Name $ip -DnsOnly -ErrorAction SilentlyContinue).NameHost) 224 | if(-not $quickNameLookup){$quickNameLookup = 'Resolving...'} 225 | $name = if ($ip -eq $internalIP) {"$hostName (This Device)"} else {"$quickNameLookup"} 226 | $vendor = if ($ip -eq $internalIP) {$myVendor} else {'Identifying...'} 227 | 228 | # Determine if the IP was pingable 229 | $pingResult = $ip -in $global:successfulPings 230 | 231 | # Format and display 232 | $item = [pscustomobject]@{ 233 | 'MACaddress' = $mac; 234 | 'Vendor' = $vendor; 235 | 'IPaddress' = $ip; 236 | 'HostName' = $name; 237 | 'Ping' = $pingResult; 238 | 'PingImage' = Create-GradientEllipse -isPingSuccessful $pingResult 239 | } 240 | $listView.Items.Add($item) 241 | } 242 | 243 | # Now add entries for successful pings not in ARP data, and add self 244 | $successfulPingsNotInARP = $global:successfulPings | Where-Object { $_ -notin $arpOutput.IPAddress } 245 | foreach ($ip in $successfulPingsNotInARP) { 246 | if ($global:gatewayPrefix -ne $originalGatewayPrefix) { 247 | $mac = 'Unreachable' 248 | } else { 249 | $mac = [MacAddressResolver]::GetMacFromIP($ip) 250 | } 251 | if ($ip -eq $internalIP) { 252 | $item = [pscustomobject]@{ 253 | 'MACaddress' = $myMac; 254 | 'Vendor' = $myVendor; 255 | 'IPaddress' = $internalIP; 256 | 'HostName' = "$hostName (This Device)"; 257 | 'Ping' = $true; 258 | 'PingImage' = Create-GradientEllipse -isPingSuccessful $true 259 | } 260 | $listView.Items.Add($item) 261 | } else { 262 | $item = [pscustomobject]@{ 263 | 'MACaddress' = $mac; 264 | 'Vendor' = $vendor; 265 | 'IPaddress' = $ip; 266 | 'HostName' = 'Resolving...'; 267 | 'Ping' = $true; 268 | 'PingImage' = Create-GradientEllipse -isPingSuccessful $true 269 | } 270 | $listView.Items.Add($item) 271 | } 272 | } 273 | 274 | # Sort ListView items by IP address in ascending order 275 | $sortedItems = $listView.Items | Sort-Object -Property {[version]$_.IPaddress} 276 | $listView.Items.Clear() 277 | $sortedItems | ForEach-Object { $listView.Items.Add($_) } 278 | $listView.Items.Refresh() 279 | 280 | if ($totalItems -ge 21) { 281 | $hostNameColumn.Width = 270 282 | } 283 | $global:totalCount = $listView.Items.Count 284 | $TotalListed.Text = "$totalCount devices found" 285 | Update-uiMain 286 | } 287 | 288 | # Background Vendor Lookup 289 | function processVendors { 290 | $runspace = [runspacefactory]::CreateRunspace() 291 | $runspace.Open() 292 | $vendorLookup = [powershell]::Create() 293 | $vendorLookup.Runspace = $runspace 294 | 295 | $lookupBlock = { 296 | param ($listView, $internalIP) 297 | 298 | $vendorJobs = @{} 299 | 300 | # Process found devices 301 | foreach ($item in $listView.Items) { 302 | $ip = $item.IPaddress 303 | $mac = $item.MACaddress 304 | if ($ip -ne $internalIP) { 305 | if($item.Vendor -eq 'Identifying...'){ 306 | $vendorJob = Start-Job -ScriptBlock { 307 | param($mac) 308 | $ProgressPreference = 'SilentlyContinue' 309 | $response = (irm "https://www.macvendorlookup.com/api/v2/$($mac.Replace(':','').Substring(0,6))" -Method Get) 310 | $ProgressPreference = 'Continue' 311 | if([string]::IsNullOrEmpty($response.Company)){ 312 | return $null 313 | } else { 314 | return $response 315 | } 316 | } -ArgumentList $mac 317 | $vendorJobs[$ip] = $vendorJob 318 | do { 319 | # Limit maximum vendor tasks and process 320 | foreach ($ipCheck in @($vendorJobs.Keys)) { 321 | if ($vendorJobs[$ipCheck].State -eq "Completed") { 322 | $result = Receive-Job -Job $vendorJobs[$ipCheck] 323 | $vendorResult = if ($result -and $result.Company) { 324 | $result.Company.substring(0, [System.Math]::Min(30, $result.Company.Length)) 325 | } else { 326 | 'Unable to Identify' 327 | } 328 | foreach ($it in $listView.Items) { 329 | if ($it.IPaddress -eq $ipCheck) { 330 | $it.Vendor = $vendorResult 331 | } 332 | } 333 | $vendorJobs.Remove($ipCheck) 334 | } 335 | } 336 | Start-Sleep -Milliseconds 50 337 | } while ($vendorJobs.Count -ge 5) 338 | } 339 | } 340 | } 341 | 342 | # Process remaining tasks 343 | while ($vendorJobs.Count -ge 1) { 344 | # Process vendor tasks 345 | foreach ($ipCheck in @($vendorJobs.Keys)) { 346 | if ($vendorJobs[$ipCheck].State -eq "Completed") { 347 | $result = Receive-Job -Job $vendorJobs[$ipCheck] 348 | $vendorResult = if ($result -and $result.Company) { 349 | $result.Company.substring(0, [System.Math]::Min(30, $result.Company.Length)) 350 | } else { 351 | 'Unable to Identify' 352 | } 353 | foreach ($it in $listView.Items) { 354 | if ($it.IPaddress -eq $ipCheck) { 355 | $it.Vendor = $vendorResult 356 | } 357 | } 358 | $vendorJobs.Remove($ipCheck) 359 | } 360 | } 361 | Start-Sleep -Milliseconds 50 362 | } 363 | 364 | # Clean up jobs 365 | Remove-Job -Job $vendorJobs.Values -Force 366 | } 367 | 368 | # Script block params 369 | $null = $vendorLookup.AddScript($lookupBlock).AddArgument($listView).AddArgument($internalIP) 370 | 371 | $asyncResult = $vendorLookup.BeginInvoke() 372 | 373 | # Cleanup 374 | $vendorLookup.EndInvoke($asyncResult) 375 | $vendorLookup.Dispose() 376 | $runspace.Close() 377 | $runspace.Dispose() 378 | } 379 | 380 | # Background Hostname Lookup 381 | function processHostnames { 382 | $hostnameLookupThread = [powershell]::Create().AddScript({ 383 | param ($listView, $internalIP, $RunspacePool, $gatewayPrefix, $originalGatewayPrefix) 384 | 385 | $pingItems = @() 386 | $nonPingItems = @() 387 | 388 | # Separate items into pingable and non-pingable 389 | foreach ($item in $listView.Items) { 390 | if ($item.Ping -eq $true -and $item.IPaddress -ne $internalIP) { 391 | $pingItems += $item 392 | } elseif ($item.IPaddress -ne $internalIP) { 393 | $nonPingItems += $item 394 | } 395 | } 396 | 397 | # Hostname resolution with timeout 398 | $timeout = if ($gatewayPrefix -ne $originalGatewayPrefix) { 4500 } else { 3000 } 399 | $resolveScript = { 400 | param ($ip, $timeout) 401 | $dnsTask = [System.Net.Dns]::GetHostEntryAsync($ip) 402 | $timeoutTask = [System.Threading.Tasks.Task]::Delay($timeout) 403 | 404 | $task = [System.Threading.Tasks.Task]::WhenAny($dnsTask, $timeoutTask) 405 | $task.Wait() 406 | $result = $task.Result 407 | 408 | if ($result -eq $dnsTask -and $dnsTask.Status -eq [System.Threading.Tasks.TaskStatus]::RanToCompletion) { 409 | return [PSCustomObject]@{IP = $ip; HostName = $dnsTask.Result.HostName} 410 | } else { 411 | return [PSCustomObject]@{IP = $ip; HostName = "Unable to Resolve"} 412 | } 413 | } 414 | 415 | # Setup separate RunspacePool 416 | $iss = [system.management.automation.runspaces.initialsessionstate]::CreateDefault() 417 | $rsHost = [runspacefactory]::CreateRunspace($iss) 418 | $rsHost.Open() 419 | $rsPool = [runspacefactory]::CreateRunspacePool(1, 10, $rsHost, $RunspacePool.ApartmentState) 420 | $rsPool.Open() 421 | 422 | # Start hostNameJobs - responses first 423 | $hostNameJobs = @() 424 | foreach ($item in $pingItems) { 425 | if($item.Hostname -eq 'Resolving...'){ 426 | $hostNameJob = [powershell]::Create().AddScript($resolveScript).AddArgument($item.IPaddress).AddArgument($timeout) 427 | $hostNameJob.RunspacePool = $rsPool 428 | $hostNameJobHandle = $hostNameJob.BeginInvoke() 429 | $hostNameJobs += [PSCustomObject]@{ 430 | Pipeline = $hostNameJob 431 | Handle = $hostNameJobHandle 432 | IP = $item.IPaddress 433 | } 434 | } 435 | } 436 | foreach ($item in $nonPingItems) { 437 | if($item.Hostname -eq 'Resolving...'){ 438 | $hostNameJob = [powershell]::Create().AddScript($resolveScript).AddArgument($item.IPaddress).AddArgument($timeout) 439 | $hostNameJob.RunspacePool = $rsPool 440 | $hostNameJobHandle = $hostNameJob.BeginInvoke() 441 | $hostNameJobs += [PSCustomObject]@{ 442 | Pipeline = $hostNameJob 443 | Handle = $hostNameJobHandle 444 | IP = $item.IPaddress 445 | } 446 | } 447 | } 448 | 449 | # Process hostNameJobs 450 | while ($hostNameJobs.Count -gt 0) { 451 | for ($i = $hostNameJobs.Count - 1; $i -ge 0; $i--) { 452 | $hostNameJob = $hostNameJobs[$i] 453 | if ($hostNameJob.Handle.IsCompleted) { 454 | $result = $hostNameJob.Pipeline.EndInvoke($hostNameJob.Handle) 455 | foreach ($it in $listView.Items) { 456 | if ($it.IPaddress -eq $hostNameJob.IP) { 457 | $it.HostName = $result.HostName 458 | break 459 | } 460 | } 461 | $hostNameJob.Pipeline.Dispose() 462 | $hostNameJobs.RemoveAt($i) 463 | } 464 | } 465 | Start-Sleep -Milliseconds 10 466 | } 467 | 468 | # Cleanup 469 | $rsPool.Close() 470 | $rsPool.Dispose() 471 | $rsHost.Close() 472 | $rsHost.Dispose() 473 | 474 | }, $true).AddArgument($listView).AddArgument($internalIP).AddArgument($RunspacePool).AddArgument($global:gatewayPrefix).AddArgument($originalGatewayPrefix) 475 | $hostnameLookupThread.RunspacePool = $RunspacePool 476 | $hostnameScan = $hostnameLookupThread.BeginInvoke() 477 | } 478 | 479 | # Portscan 480 | function Test-Port { 481 | param ( 482 | [string]$computer, 483 | [int]$port, 484 | [int]$timeout = 5 485 | ) 486 | $tcp = New-Object System.Net.Sockets.TcpClient 487 | try { 488 | $result = $tcp.BeginConnect($computer, $port, $null, $null) 489 | $success = $result.AsyncWaitHandle.WaitOne($timeout, $true) 490 | if ($success) { 491 | return "Port`: $port is open" 492 | } 493 | else { 494 | return $null 495 | } 496 | } 497 | catch { 498 | return $null 499 | } 500 | finally { 501 | $tcp.Close() 502 | } 503 | } 504 | 505 | # Check common ports 506 | function CheckConnectivity { 507 | param ( 508 | [string]$selectedhost 509 | ) 510 | # Disable all buttons for 'This Device' 511 | if ($selectedhost -match $internalIP) { 512 | @('btnRDP', 'btnWebInterface', 'btnShare') | ForEach-Object { 513 | Get-Variable $_ -ValueOnly | ForEach-Object { 514 | $_.IsEnabled = $false 515 | $_.Visibility = 'Collapsed' 516 | } 517 | } 518 | $btnNone.IsEnabled = $true 519 | $btnNone.Visibility = 'Visible' 520 | return 521 | } 522 | $global:tryToConnect = $selectedhost -replace ' (This Device)', '' 523 | 524 | # Find the item in ListView based on IP or HostName 525 | $selectedItem = $listView.Items | Where-Object { 526 | $_.IPaddress -eq $tryToConnect -or $_.HostName -eq $selectedhost 527 | } | Select-Object -First 1 528 | 529 | # Check connectivity for different protocols 530 | $ports = @{ 531 | HTTP = 80 532 | HTTPS = 443 533 | SMBv2 = 445 534 | SMB = 139 535 | RDP = 3389 536 | } 537 | $results = @{} 538 | foreach ($protocol in $ports.Keys) { 539 | $results[$protocol] = Test-Port -computer $tryToConnect -port $ports[$protocol] -timeout 200 540 | } 541 | 542 | # Update button states based on connectivity results 543 | $btnShare.IsEnabled = ($results.SMBv2 -or $results.SMB) -and $HostName -ne $tryToConnect 544 | $btnShare.Visibility = if ($btnShare.IsEnabled) { 'Visible' } else { 'Collapsed' } 545 | 546 | if ($btnShare.Visibility -eq 'Visible') {$btnWebInterface.Margin = "0,0,25,0"} else {$btnWebInterface.Margin = "0,0,0,0"} 547 | $btnWebInterface.IsEnabled = ($results.HTTP -or $results.HTTPS) -and $HostName -ne $tryToConnect 548 | $btnWebInterface.Visibility = if ($btnWebInterface.IsEnabled) { 'Visible' } else { 'Collapsed' } 549 | $global:httpAvailable = if ($results.HTTP) { 1 } else { 0 } 550 | 551 | if ($btnShare.Visibility -eq 'Visible' -or $btnWebInterface.Visibility -eq 'Visible') {$btnRDP.Margin = "0,0,25,0"} else {$btnRDP.Margin = "0,0,0,0"} 552 | $btnRDP.IsEnabled = $results.RDP -and $HostName -ne $tryToConnect 553 | $btnRDP.Visibility = if ($btnRDP.IsEnabled) { 'Visible' } else { 'Collapsed' } 554 | 555 | # Show no connections icon if nothing is available 556 | if (-not $btnRDP.IsEnabled -and -not $btnWebInterface.IsEnabled -and -not $btnShare.IsEnabled) { 557 | $btnNone.IsEnabled = $true 558 | $btnNone.Visibility = 'Visible' 559 | } else { 560 | $btnNone.IsEnabled = $false 561 | $btnNone.Visibility = 'Collapsed' 562 | } 563 | 564 | # Show ping response status in popup window 565 | $pingStatusImage.Content = Create-GradientEllipse -isPingSuccessful $selectedItem.Ping -width 12 -height 12 566 | $pingStatusText.Text = if ($selectedItem.Ping) { "ICMP response received" } else { "No ICMP response received" } 567 | } 568 | 569 | # Listview column sort logic 570 | $sortDirections = @{} 571 | $listViewSortColumn = { 572 | param([System.Object]$sender, [System.EventArgs]$Event) 573 | $column = $Event.OriginalSource.Column 574 | 575 | # Determine current direction, toggle if column has been sorted before 576 | switch ($true) { 577 | {$sortDirections.ContainsKey($column.Header)} { 578 | $sortDirections[$column.Header] = -not $sortDirections[$column.Header] 579 | } 580 | default { 581 | # false for descending, true for ascending 582 | $sortDirections[$column.Header] = $false 583 | } 584 | } 585 | $direction = if ($sortDirections[$column.Header]) { "Ascending" } else { "Descending" } 586 | 587 | # Sort items 588 | $sortedItems = switch ($column.Header) { 589 | "IP Address" { 590 | $Sender.Items | Sort-Object -Property {[version]$_.IPaddress} -Descending:($direction -eq "Descending") 591 | } 592 | default { 593 | if ($column.DisplayMemberBinding.Path.Path) { 594 | $Sender.Items | Sort-Object -Property $column.DisplayMemberBinding.Path.Path -Descending:($direction -eq "Descending") 595 | } else { 596 | $Sender.Items 597 | } 598 | } 599 | } 600 | # Rebuild sorted list 601 | $Sender.Items.Clear() 602 | $sortedItems | ForEach-Object { $Sender.Items.Add($_) } 603 | } 604 | 605 | function Create-GradientEllipse { 606 | param ( 607 | [bool]$isPingSuccessful, 608 | [double]$width = 9, 609 | [double]$height = 9 610 | ) 611 | 612 | $ellipse = [Windows.Shapes.Ellipse]::new() 613 | $ellipse.Width = $width 614 | $ellipse.Height = $height 615 | 616 | if ($isPingSuccessful) { 617 | # Lighter blue gradient for successful ping 618 | $gradient = New-Object System.Windows.Media.RadialGradientBrush 619 | $gradient.GradientOrigin = New-Object System.Windows.Point(0.5, 0.5) 620 | $gradient.Center = New-Object System.Windows.Point(0.5, 0.5) 621 | $gradient.RadiusX = 0.5 622 | $gradient.RadiusY = 0.5 623 | $stop1 = New-Object System.Windows.Media.GradientStop 624 | $stop1.Color = [System.Windows.Media.Color]::FromArgb(255, 51, 204, 255) 625 | $stop1.Offset = 0 626 | $gradient.GradientStops.Add($stop1) 627 | $stop2 = New-Object System.Windows.Media.GradientStop 628 | $stop2.Color = [System.Windows.Media.Color]::FromArgb(255, 25, 153, 204) 629 | $stop2.Offset = 0.8 630 | $gradient.GradientStops.Add($stop2) 631 | $stop3 = New-Object System.Windows.Media.GradientStop 632 | $stop3.Color = [System.Windows.Media.Color]::FromArgb(255, 0, 102, 153) 633 | $stop3.Offset = 1 634 | $gradient.GradientStops.Add($stop3) 635 | } else { 636 | # Shades of gray for unsuccessful ping 637 | $gradient = New-Object System.Windows.Media.RadialGradientBrush 638 | $gradient.GradientOrigin = New-Object System.Windows.Point(0.5, 0.5) 639 | $gradient.Center = New-Object System.Windows.Point(0.5, 0.5) 640 | $gradient.RadiusX = 0.5 641 | $gradient.RadiusY = 0.5 642 | $stop4 = New-Object System.Windows.Media.GradientStop 643 | $stop4.Color = [System.Windows.Media.Color]::FromArgb(255, 220, 220, 220) 644 | $stop4.Offset = 0 645 | $gradient.GradientStops.Add($stop4) 646 | $stop5 = New-Object System.Windows.Media.GradientStop 647 | $stop5.Color = [System.Windows.Media.Color]::FromArgb(255, 160, 160, 160) 648 | $stop5.Offset = 0.8 649 | $gradient.GradientStops.Add($stop5) 650 | $stop6 = New-Object System.Windows.Media.GradientStop 651 | $stop6.Color = [System.Windows.Media.Color]::FromArgb(255, 100, 100, 100) 652 | $stop6.Offset = 1 653 | $gradient.GradientStops.Add($stop6) 654 | } 655 | 656 | $ellipse.Fill = $gradient 657 | return $ellipse 658 | } 659 | 660 | # Display network speed as KB/s -MB/s -GB/s 661 | function Format-Speed { 662 | param ([double]$speedInKBs) 663 | if ($speedInKBs -ge 1024 * 1024) { return "{0:N1}gb" -f ($speedInKBs / (1024 * 1024)) } 664 | elseif ($speedInKBs -ge 1024) { return "{0:N1}mb" -f ($speedInKBs / 1024) } 665 | else { return "{0:N0}kb" -f $speedInKBs } 666 | } 667 | 668 | # Initialize hashtable for Monitor Mode 669 | $global:syncHash = [Hashtable]::Synchronized(@{ 670 | NetworkStats = @{} 671 | TCPConnections = @() 672 | LastUpdate = [DateTime]::Now 673 | Error = $null 674 | }) 675 | 676 | function Start-NetMonBackgroundTask { 677 | $backgroundScript = { 678 | param($syncHash, $adapters) 679 | function Get-NetworkStatsWithTimeout { 680 | $statsPerAdapter = @{} 681 | $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() 682 | $timeoutMs = 100 683 | $retryCount = 2 684 | for ($i = 0; $i -lt $retryCount; $i++) { 685 | try { 686 | $currentAdapters = Get-NetAdapter | Where-Object { $_.Status -eq "Up" -and $_.InterfaceDescription -notlike "*Loopback*" -and $_.InterfaceDescription -notlike "*ISATAP*" } 687 | if (-not $currentAdapters) { 688 | return $statsPerAdapter 689 | } 690 | foreach ($adapter in $currentAdapters) { 691 | $adapterName = $adapter.Name 692 | $stats = Get-NetAdapterStatistics -Name $adapterName -ErrorAction SilentlyContinue 693 | if ($stats) { 694 | $statsPerAdapter[$adapterName] = @{ 695 | RxBytes = [double]$stats.ReceivedBytes 696 | TxBytes = [double]$stats.SentBytes 697 | Timestamp = [double](Get-Date).Ticks 698 | } 699 | } else { 700 | $global:syncHash.Error = "No stats returned for adapter: $adapterName" 701 | } 702 | } 703 | if ($statsPerAdapter.Count -gt 0) { break } 704 | Start-Sleep -Milliseconds 100 705 | } catch { 706 | $global:syncHash.Error = "Network stats error: $_" 707 | } 708 | $stopwatch.Stop() 709 | if ($stopwatch.ElapsedMilliseconds -gt $timeoutMs) { break } 710 | $stopwatch.Restart() 711 | } 712 | return $statsPerAdapter 713 | } 714 | function Get-TCPConnections { 715 | try { 716 | $connections = Get-NetTCPConnection | Where-Object { $_.State -eq "Established" } 717 | $tcpList = @() 718 | foreach ($conn in $connections) { 719 | $process = Get-CimInstance Win32_Process -Filter "ProcessId = $($conn.OwningProcess)" -ErrorAction SilentlyContinue 720 | $processName = if ($process) { $process.Name } else { "Unknown" } 721 | $tcpList += [PSCustomObject]@{ 722 | LocalAddress = $conn.LocalAddress; LocalPort = $conn.LocalPort; RemoteAddress = $conn.RemoteAddress; RemotePort = $conn.RemotePort; ProcessName = $processName 723 | } 724 | } 725 | return $tcpList 726 | } catch { 727 | $global:syncHash.Error = "TCP connections error: $_" 728 | return @() 729 | } 730 | } 731 | while ($true) { 732 | try { 733 | $stats = Get-NetworkStatsWithTimeout 734 | $tcp = Get-TCPConnections 735 | $global:syncHash.NetworkStats = $stats 736 | $global:syncHash.TCPConnections = $tcp 737 | $global:syncHash.LastUpdate = [DateTime]::Now 738 | $global:syncHash.Error = $null 739 | } catch { 740 | Write-Host "Background task error: $_" 741 | $global:syncHash.Error = $_.ToString() 742 | } 743 | Start-Sleep -Milliseconds 1000 744 | } 745 | } 746 | $runspace = [PowerShell]::Create().AddScript($backgroundScript).AddArgument($syncHash).AddArgument($adapters) 747 | $runspace.RunspacePool = $RunspacePool 748 | return $runspace.BeginInvoke() 749 | } 750 | 751 | # Direct MAC request via iphlpapi.dll 752 | Add-Type -TypeDefinition @" 753 | using System; 754 | using System.Collections.Generic; 755 | using System.Runtime.InteropServices; 756 | 757 | public class MacAddressResolver 758 | { 759 | [DllImport("iphlpapi.dll", ExactSpelling = true)] 760 | public static extern int SendARP(uint DestIP, uint SrcIP, byte[] pMacAddr, ref int PhyAddrLen); 761 | 762 | public static string GetMacFromIP(string ipAddress) 763 | { 764 | try 765 | { 766 | System.Net.IPAddress ip = System.Net.IPAddress.Parse(ipAddress); 767 | byte[] macAddr = new byte[6]; 768 | int macAddrLen = macAddr.Length; 769 | if (SendARP(BitConverter.ToUInt32(ip.GetAddressBytes(), 0), 0, macAddr, ref macAddrLen) == 0) 770 | { 771 | string[] str = new string[macAddr.Length]; 772 | for (int i = 0; i < macAddr.Length; i++) 773 | { 774 | str[i] = macAddr[i].ToString("X2"); 775 | } 776 | return string.Join(":", str); 777 | } 778 | else 779 | { 780 | return "Unknown"; 781 | } 782 | } 783 | catch 784 | { 785 | return "Unknown"; 786 | } 787 | } 788 | } 789 | "@ 790 | 791 | # Get icons from DLL or EXE files via shell32.dll 792 | $getIcons = @" 793 | using System; 794 | using System.Drawing; 795 | using System.Runtime.InteropServices; 796 | using System.Windows.Interop; 797 | using System.Windows.Media.Imaging; 798 | using System.Windows; 799 | 800 | namespace System 801 | { 802 | public class IconExtractor 803 | { 804 | public static Icon Extract(string file, int number, bool largeIcon) 805 | { 806 | IntPtr large; 807 | IntPtr small; 808 | ExtractIconEx(file, number, out large, out small, 1); 809 | try 810 | { 811 | return Icon.FromHandle(largeIcon ? large : small); 812 | } 813 | catch 814 | { 815 | return null; 816 | } 817 | } 818 | public static BitmapSource IconToBitmapSource(Icon icon) 819 | { 820 | return Imaging.CreateBitmapSourceFromHIcon( 821 | icon.Handle, 822 | Int32Rect.Empty, 823 | BitmapSizeOptions.FromEmptyOptions()); 824 | } 825 | [DllImport("Shell32.dll", EntryPoint = "ExtractIconExW", CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] 826 | private static extern int ExtractIconEx(string sFile, int iIndex, out IntPtr piLargeVersion, out IntPtr piSmallVersion, int amountIcons); 827 | } 828 | } 829 | "@ 830 | 831 | # Define WPF GUI Structure 832 | Add-Type -TypeDefinition $getIcons -ReferencedAssemblies System.Windows.Forms, System.Drawing, PresentationCore, PresentationFramework, WindowsBase 833 | [xml]$XAML = @' 834 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 889 | 924 | 940 | 958 | 991 | 1040 | 1085 | 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | 1149 | 1150 | 1151 | 1152 | 1153 | 1154 | 1155 | 1156 | 1157 | 1158 | 1159 | 1160 | 1161 | 1162 | 1163 | 1164 | 1165 | 1166 | 1167 | 1168 | 1218 | 1219 | 1220 | 1221 | 1222 | 1223 | 1224 | 1225 | 1226 | 1227 | 1228 | 1229 | 1230 | 1231 | 1232 | 1233 | 1234 | 1235 | 1236 | 1237 | 1238 | 1239 | 1240 | 1286 | 1287 | 1288 | 1289 | 1306 | 1307 | 1308 | 1309 | 1310 | 1311 | 1312 | 1313 | 1314 | 1315 | 1316 | 1317 | 1318 | 1319 | 1320 | 1321 | 1322 | 1323 | 1324 | 1325 | 1326 | 1327 | 1328 | 1329 | 1330 | 1331 | 1332 | 1333 | 1334 | 1335 | 1336 | 1337 | 1338 | 1339 | 1340 | 1341 | 1342 | 1343 | 1344 | 1345 | 1346 | 1347 | 1348 | 1349 | 1350 | 1351 | 1352 | 1353 | 1354 | 1406 | 1409 | 1432 | 1433 | 1434 | 1435 | 1436 | 1437 | 1477 | 1478 | 1479 | 1480 | 1481 | 1482 | 1483 | 1484 | 1485 | 1486 | 1487 | 1488 | 1489 | 1490 | 1491 | 1492 | 1493 | 1494 | 1495 | 1496 | 1497 | 1498 | 1499 | 1500 | 1501 | 1502 | 1503 | 1504 | 1505 | 1506 | 1507 | 1508 | 1509 | 1510 | 1511 | 1512 | 1513 | 1514 | 1515 | 1516 | 1517 | 1518 | 1519 | 1520 | 1521 | 1522 | 1523 | 1524 | 1525 | 1526 | 1560 | 1561 | 1562 | 1563 | 1564 | 1584 | 1604 | 1624 | 1658 | 1659 | 1660 | 1830 | 1831 | 1832 | 1840 | 1841 | 1842 | 1843 | 1844 | 1845 | 1846 | 1847 | 1848 | 1888 | 1889 | 2 1890 | 1891 |