# ========================================================= # MAX 网络工具箱 - 局域网发现 # LAN 设备扫描 / ARP 表查看与导出 # ========================================================= $Common = "C:\Setup\windows\lib\common.ps1" if (Test-Path $Common) { . $Common } # ========================================================= # 辅助函数 # ========================================================= function Get-PrimaryIPv4Info { try { $Config = Get-CimInstance Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled -eq $true -and $_.IPAddress -ne $null -and $_.DefaultIPGateway -ne $null } | Select-Object -First 1 if ($null -eq $Config) { return $null } $IPv4 = $Config.IPAddress | Where-Object { $_ -match '^\d{1,3}(\.\d{1,3}){3}$' -and $_ -notlike '169.254.*' } | Select-Object -First 1 $Gateway = $Config.DefaultIPGateway | Where-Object { $_ -match '^\d{1,3}(\.\d{1,3}){3}$' } | Select-Object -First 1 if ([string]::IsNullOrWhiteSpace($IPv4)) { return $null } $Parts = $IPv4.Split(".") $SubnetPrefix = "$($Parts[0]).$($Parts[1]).$($Parts[2])" $DefaultSubnet = "$SubnetPrefix.0/24" return [PSCustomObject]@{ IPv4 = $IPv4 Gateway = $Gateway SubnetPrefix = $SubnetPrefix DefaultSubnet = $DefaultSubnet } } catch { return $null } } function Get-SubnetPrefixFromInput { param([string]$InputValue) $InputValue = $InputValue.Trim() if ($InputValue -match '^(\d{1,3}\.\d{1,3}\.\d{1,3})\.0/24$') { return $Matches[1] } if ($InputValue -match '^(\d{1,3}\.\d{1,3}\.\d{1,3})$') { return $Matches[1] } if ($InputValue -match '^(\d{1,3}\.\d{1,3}\.\d{1,3})\.\d{1,3}$') { return $Matches[1] } return $null } function Resolve-HostNameSafe { param([string]$TargetIP) try { $Result = [System.Net.Dns]::GetHostEntry($TargetIP) if ($Result -and $Result.HostName) { return $Result.HostName } } catch {} return "" } function Get-MacFromArp { param([string]$TargetIP) try { $ArpOutput = arp -a $TargetIP 2>$null foreach ($Line in $ArpOutput) { if ($Line -match [regex]::Escape($TargetIP)) { $Columns = $Line -split '\s+' foreach ($Column in $Columns) { if ($Column -match '^([0-9a-fA-F]{2}-){5}[0-9a-fA-F]{2}$') { return $Column } } } } } catch {} return "" } function Test-IPv4Address { param([string]$Address) if ([string]::IsNullOrWhiteSpace($Address)) { return $false } if ($Address -notmatch '^\d{1,3}(\.\d{1,3}){3}$') { return $false } $Parts = $Address.Split(".") foreach ($Part in $Parts) { $Number = 0 if (-not [int]::TryParse($Part, [ref]$Number)) { return $false } if ($Number -lt 0 -or $Number -gt 255) { return $false } } return $true } # ========================================================= # 扫描函数 # ========================================================= function Scan-SubnetFast { param([string]$SubnetPrefix) Write-Section "快速扫描 $SubnetPrefix.0/24" $Results = @() for ($i = 1; $i -le 254; $i++) { $TargetIP = "$SubnetPrefix.$i" Write-Host "`r 进度: $i / 254" -NoNewline -ForegroundColor DarkGray try { $Online = Test-Connection -ComputerName $TargetIP -Count 1 -Quiet -ErrorAction SilentlyContinue } catch { $Online = $false } if ($Online) { $Mac = Get-MacFromArp -TargetIP $TargetIP $Item = [PSCustomObject]@{ IP = $TargetIP Status = "Online" MAC = $Mac HostName = "" } $Results += $Item Write-OK "在线: $TargetIP MAC: $Mac" } } Write-Host "" Write-OK "快速扫描完成,在线设备: $($Results.Count)" if ($Results.Count -gt 0) { $Results | Format-Table -AutoSize } return $Results } function Scan-SubnetDeep { param([string]$SubnetPrefix) Write-Section "深度扫描 $SubnetPrefix.0/24" Write-Warn "深度扫描会尝试获取 MAC 和主机名,速度较慢" $Results = @() for ($i = 1; $i -le 254; $i++) { $TargetIP = "$SubnetPrefix.$i" Write-Host "`r 进度: $i / 254" -NoNewline -ForegroundColor DarkGray try { $Online = Test-Connection -ComputerName $TargetIP -Count 1 -Quiet -ErrorAction SilentlyContinue } catch { $Online = $false } if ($Online) { $Mac = Get-MacFromArp -TargetIP $TargetIP $Name = Resolve-HostNameSafe -TargetIP $TargetIP $Item = [PSCustomObject]@{ IP = $TargetIP Status = "Online" MAC = $Mac HostName = $Name } $Results += $Item Write-OK "在线: $TargetIP MAC: $Mac 主机名: $Name" } } Write-Host "" Write-OK "深度扫描完成,在线设备: $($Results.Count)" if ($Results.Count -gt 0) { $Results | Format-Table -AutoSize } return $Results } function Show-ArpTable { Write-Section "查看 ARP 表" try { arp -a } catch { Write-Err "读取 ARP 表失败" Write-Muted $_.Exception.Message } } function Export-ArpTable { Write-Section "导出 ARP 表" $ExportDir = "C:\Setup\Logs" New-Item -ItemType Directory -Path $ExportDir -Force | Out-Null $Time = Get-Date -Format "yyyyMMdd-HHmmss" $TxtFile = Join-Path $ExportDir "arp-table-$Time.txt" $CsvFile = Join-Path $ExportDir "arp-table-$Time.csv" try { $ArpOutput = arp -a $ArpOutput | Out-File -FilePath $TxtFile -Encoding UTF8 $Rows = @() foreach ($Line in $ArpOutput) { if ($Line -match '^\s*(\d{1,3}(\.\d{1,3}){3})\s+(([0-9a-fA-F]{2}-){5}[0-9a-fA-F]{2})\s+(\w+)') { $Rows += [PSCustomObject]@{ IP = $Matches[1] MAC = $Matches[3] Type = $Matches[5] } } } if ($Rows.Count -gt 0) { $Rows | Export-Csv -Path $CsvFile -NoTypeInformation -Encoding UTF8 } Write-OK "ARP 表已导出:" Write-Info $TxtFile Write-Info $CsvFile } catch { Write-Err "导出 ARP 表失败" Write-Muted $_.Exception.Message } } function Export-ScanResults { param([array]$Results) if (-not $Results -or $Results.Count -eq 0) { Write-Warn "没有可导出的扫描结果" return } $ExportDir = "C:\Setup\Logs" New-Item -ItemType Directory -Path $ExportDir -Force | Out-Null $Time = Get-Date -Format "yyyyMMdd-HHmmss" $CsvFile = Join-Path $ExportDir "lan-scan-$Time.csv" try { $Results | Export-Csv -Path $CsvFile -NoTypeInformation -Encoding UTF8 Write-OK "扫描结果已导出:" Write-Info $CsvFile } catch { Write-Err "导出扫描结果失败" Write-Muted $_.Exception.Message } } # ========================================================= # 主循环 # ========================================================= $LastScanResults = @() do { $Info = Get-PrimaryIPv4Info Write-Header "局域网发现" if ($Info) { Write-Info "本机 IP: $($Info.IPv4)" Write-Info "默认网关: $($Info.Gateway)" Write-Info "默认网段: $($Info.DefaultSubnet)" } else { Write-Warn "未能自动获取本机 IPv4 信息" } Write-Line Write-Section "操作菜单" Write-Info "1. 快速扫描默认网段" Write-Info "2. 深度扫描默认网段" Write-Info "3. 自定义 /24 网段扫描" Write-Info "4. 查看 ARP 表" Write-Info "5. 导出 ARP 表" Write-Info "6. 导出上次扫描结果" Write-Info "0. 退出" Write-Host "" $Choice = Read-Host " 请选择" Write-Line switch ($Choice.Trim()) { "1" { if ($Info) { $LastScanResults = Scan-SubnetFast -SubnetPrefix $Info.SubnetPrefix } else { Write-Err "无法获取默认网段" } Pause-Max } "2" { if ($Info) { $LastScanResults = Scan-SubnetDeep -SubnetPrefix $Info.SubnetPrefix } else { Write-Err "无法获取默认网段" } Pause-Max } "3" { $InputSubnet = Read-Host " 请输入网段,例如 192.168.1.0/24 或 192.168.1" $SubnetPrefix = Get-SubnetPrefixFromInput -InputValue $InputSubnet if ([string]::IsNullOrWhiteSpace($SubnetPrefix)) { Write-Err "网段格式不正确" } else { Write-Host "" Write-Info "1. 快速扫描" Write-Info "2. 深度扫描" Write-Host "" $Mode = Read-Host " 请选择扫描模式" if ($Mode.Trim() -eq "2") { $LastScanResults = Scan-SubnetDeep -SubnetPrefix $SubnetPrefix } else { $LastScanResults = Scan-SubnetFast -SubnetPrefix $SubnetPrefix } } Pause-Max } "4" { Show-ArpTable Pause-Max } "5" { Export-ArpTable Pause-Max } "6" { Export-ScanResults -Results $LastScanResults Pause-Max } "0" { Write-OK "已退出" break } default { Write-Warn "无效选项" Pause-Max } } } while ($Choice -ne "0")