You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PowerShell WinForm端口扫描脚本卡顿及格式化输出问题

端口扫描脚本问题排查与优化

问题描述

端口扫描结果需输出至$TextBoxResultScan,当前存在两个核心问题:

  • 端口开放时可输出结果,但格式不符合预期;
  • 端口关闭时,脚本及Windows Form窗体出现卡顿冻结现象。

需解决以下两点:

  1. 分析卡顿冻结的原因并提供修复方案;
  2. 配置扫描结果按服务器地址、端口号、扫描结果三列格式化输出。

问题1:卡顿冻结的原因与修复

原因分析

  1. UI线程阻塞:Test-NetConnection在端口关闭时会等待默认超时(通常数秒),而扫描逻辑直接在窗体的UI线程中执行,导致UI无法响应任何操作,出现冻结。
  2. 串行执行效率低:所有端口扫描是逐个串行执行的,累计等待时间过长,进一步加剧了UI阻塞。

修复方案

  1. 后台线程执行扫描:将扫描逻辑放到后台线程中,避免占用UI线程,同时通过Control.Invoke安全更新UI控件(WinForm控件仅允许创建它的线程修改)。
  2. 缩短超时时间:给Test-NetConnection添加-TimeoutMs参数,将超时设置为1000毫秒(可根据需求调整),减少等待时长。
  3. 可选优化:并行扫描:使用ForEach-Object -Parallel(PowerShell 7+)或Start-Job实现并行端口扫描,大幅提升扫描速度。

问题2:按列格式化输出结果

当前问题

直接将Select-Object的输出赋值给TextBoxResultScan.Text,PowerShell会自动将对象转换为默认字符串格式,导致排版混乱,且每次赋值会覆盖之前的结果,最终仅显示最后一条扫描记录。

实现方法

  1. 添加表头:扫描开始前先写入列标题,用制表符\t分隔列,保证对齐。
  2. 逐行追加结果:使用+=将每条扫描结果追加到文本框,而非覆盖。
  3. 格式化每行内容:手动拼接服务器地址、端口号、扫描结果(将布尔值转换为更直观的"开放"/"关闭"),用制表符分隔保证列对齐;同时使用等宽字体进一步优化排版效果。

修改后的完整脚本

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

<#------======== Define Variables ========------#>
$MyForest = ([System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()).Name
$MyDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name

$NetworkPorts = @('21','22','23','25','38','42','53','65','69','80','118','123','135','137','139','143','145','156','161','170','389','443','445','647','993','995','1433','1540','1541','3389','8080')
<#------======== Define Variables ========------#>


<#------======== Common Form ========------#>
$CommonForm = New-Object System.Windows.Forms.Form
$CommonForm.AutoSize = $true
$CommonForm.Text = "Servers Scanning"
<#------======== Common Form ========------#>
<#------======== Label List Enable Servers ========------#>
$LabelListeServers = New-Object System.Windows.Forms.Label
$LabelListeServers.Location = '2,10'
$LabelListeServers.AutoSize = $true
$LabelListeServers.Text = 'Enable Servers Scanning'
$CommonForm.Controls.Add($LabelListeServers)
<#------======== Label List Enable Servers ========------#>
<#------======== ListBox List Enable Servers ========------#>
$ListboxListeServers = New-Object System.Windows.Forms.ListBox
$ListboxListeServers.Location = '2,30'
$ListboxListeServers.Size = New-Object System.Drawing.Size(200,125)
$CommonForm.Controls.Add($ListboxListeServers)
<#------======== ListBox List Enable Servers ========------#>
<#------======== Button Search Enable Servers ========------#>
$ButtonSearcheServers = New-Object System.Windows.Forms.Button
$ButtonSearcheServers.Location = '2, 165'
$ButtonSearcheServers.Text = 'Find Servers'
$ButtonSearcheServers.AutoSize = $true
$ButtonSearcheServers.add_Click({
    $script:eADWindowsServers = @((Get-ADComputer -Server $MyDomain -Filter {(OperatingSystem -like '*Windows Server*') -and (Enabled -eq $true)} | sort Name).DnsHostName)
    $ListboxListeServers.Items.Clear()
    Foreach($eADWindowsServer in $eADWindowsServers){
        $ListboxListeServers.Items.Add($eADWindowsServer)
    }
})

$CommonForm.Controls.Add($ButtonSearcheServers)
<#------======== Button Search Enable Servers ========------#>
<#------======== Label List Disable Servers ========------#>
$LabelListdServers = New-Object System.Windows.Forms.Label
$LabelListdServers.Location = '2,195'
$LabelListdServers.AutoSize = $true
$LabelListdServers.Text = 'Disable Servers Scanning'
$CommonForm.Controls.Add($LabelListdServers)
<#------======== Label List Disable Servers ========------#>
<#------======== ListBox List Disable Servers ========------#>
$ListboxListdServers = New-Object System.Windows.Forms.ListBox
$ListboxListdServers.Location = '2,220'
$ListboxListdServers.Size = New-Object System.Drawing.Size(195,125)
$CommonForm.Controls.Add($ListboxListdServers)
<#------======== ListBox List Disable Servers ========------#>
<#------======== Button Search Disable Servers ========------#>
$ButtonSearchdServers = New-Object System.Windows.Forms.Button
$ButtonSearchdServers.Location = '2, 350'
$ButtonSearchdServers.Text = 'Find Servers'
$ButtonSearchdServers.AutoSize = $true
$ButtonSearchdServers.add_Click({
    $dADWindowsServers = @((Get-ADComputer -Server $MyDomain -Filter {(OperatingSystem -like '*Windows Server*') -and (Enabled -eq $false)} | sort Name).DnsHostName)
    $ListboxListdServers.Items.Clear()
    Foreach($dADWindowsServer in $dADWindowsServers){
        $ListboxListdServers.Items.Add($dADWindowsServer)
    }
})

$CommonForm.Controls.Add($ButtonSearchdServers)
<#------======== Button Search Disable Servers ========------#>
<#------======== Label Port Scan ========------#>
$LabelPortScan = New-Object System.Windows.Forms.Label
$LabelPortScan.Location = '225,10'
$LabelPortScan.AutoSize = $true
$LabelPortScan.Text = 'Ports scanning. Port:'
$CommonForm.Controls.Add($LabelPortScan)
<#------======== TextBox Result Scan ========------#>
$TextBoxResultScan = New-Object System.Windows.Forms.TextBox
$TextBoxResultScan.Location = '225,30'
$TextBoxResultScan.Size = New-Object System.Drawing.Size(565,200)
$TextBoxResultScan.Multiline = $true
$TextBoxResultScan.Font = New-Object System.Drawing.Font("Consolas", 10) # 使用等宽字体保证列对齐
$CommonForm.Controls.Add($TextBoxResultScan)
<#------======== TextBox Result Scan ========------#>
<#------======== TextBox Enter Port ========------#>
$TextBoxPortScan = New-Object System.Windows.Forms.TextBox
$TextBoxPortScan.Location = '360,7'
$TextBoxPortScan.Size = New-Object System.Drawing.Size(40,20) # 修正高度,原高度5看不见
$CommonForm.Controls.Add($TextBoxPortScan)
<#------======== TextBox Enter Port ========------#>
<#------======== Button Port Scan ========------#>
$ButtonPortScan = New-Object System.Windows.Forms.Button
$ButtonPortScan.Location = '400, 5'
$ButtonPortScan.Text = 'Check'
$ButtonPortScan.AutoSize = $true
$ButtonPortScan.add_Click({
    # 禁用按钮防止重复点击
    $ButtonPortScan.Enabled = $false
    # 清空结果框并添加表头
    $TextBoxResultScan.Invoke({
        $this.Text = "服务器地址`t端口号`t扫描结果`n"
    })

    # 确定扫描目标:服务器和端口
    $targetServers = if($ListboxListeServers.SelectedItem -eq $null) { $script:eADWindowsServers } else { @($ListboxListeServers.SelectedItem.ToString()) }
    $targetPorts = if($TextBoxPortScan.Text -ne '') { @($TextBoxPortScan.Text) } else { $NetworkPorts }

    # 启动后台线程执行扫描
    $job = Start-Job -ScriptBlock {
        param($Servers, $Ports, $TimeoutMs)
        $results = @()
        foreach($server in $Servers){
            foreach($port in $Ports){
                $result = Test-NetConnection -ComputerName $server -Port $port -TimeoutMs $TimeoutMs -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
                $status = if($result.TcpTestSucceeded) { "开放" } else { "关闭" }
                # 处理无法解析地址的情况
                $remoteAddr = if($result.RemoteAddress) { $result.RemoteAddress } else { "无法解析" }
                $results += [PSCustomObject]@{
                    Server = $server
                    Port = $port
                    Status = $status
                    RemoteAddress = $remoteAddr
                }
            }
        }
        return $results
    } -ArgumentList $targetServers, $targetPorts, 1000

    # 轮询作业状态,刷新UI防止假死
    while($job.State -eq 'Running'){
        Start-Sleep -Milliseconds 100
        $CommonForm.Refresh()
    }

    # 获取作业结果并更新UI
    $scanResults = Receive-Job -Job $job
    Remove-Job -Job $job

    foreach($item in $scanResults){
        $line = "$($item.RemoteAddress)`t$($item.Port)`t$($item.Status)`n"
        $TextBoxResultScan.Invoke({
            $this.Text += $line
        })
    }

    # 重新启用按钮
    $ButtonPortScan.Enabled = $true
})

$CommonForm.Controls.Add($ButtonPortScan)
<#------======== Button Port Scan ========------#>
<#------======== Call Form ========------#>
$CommonForm.ShowDialog()
<#------======== Call Form ========------#>

内容的提问来源于stack exchange,提问作者Catcher Rem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 17:13:09