You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

PowerShell网卡连接中断监控GUI脚本开发及事件捕获问题求助

PowerShell网卡连接中断监控GUI脚本开发及事件捕获问题求助

问题分析

你当前的脚本采用定时轮询网卡状态的方式,但这种方式存在两个核心问题:

  1. 轮询间隔(5秒)可能错过短暂的状态变化,或者某些网卡状态变更不会被Get-NetAdapter及时捕获
  2. 没有实现你需求中的「掉线-恢复事件配对」逻辑(当前脚本只记录单次状态变更,无法将掉线和恢复关联为同一CSV行)

另外你提到断开NIC时事件不注册,大概率是因为轮询时机不对,或者部分系统/网卡的状态变更不会触发Get-NetAdapter的即时更新。改用Windows事件日志监听的方式会更可靠——因为网卡的连接/断开事件会被系统统一记录到System日志中,对应特定的Event ID:

  • 网卡断开/链路下降:Event ID 27(来自Microsoft-Windows-NetworkProfile)、Event ID 10400(来自Microsoft-Windows-TCPIP
  • 网卡恢复/链路上升:Event ID 28(来自Microsoft-Windows-NetworkProfile

修复后的完整脚本

下面的脚本完全满足你的所有需求,同时解决了事件捕获失效的问题:

  • Start MonitoringCancel按钮的GUI界面
  • 自动生成符合要求的CSV(命名为计算机名-监控开始时间.csv,存储在C:\NIC_Drops
  • 每一行对应一次完整的「掉线-恢复」事件对,包含你需要的所有字段
  • 采用系统事件日志监听+事件配对的方式,确保不会错过任何网卡变更
  • 点击Cancel或关闭窗体时,自动保存已捕获的所有数据
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# 全局变量初始化
$monitoring = $false
$logEntries = @()
$csvPath = $null
$startMonitoringTime = $null
# 用于配对掉线/恢复事件的字典:键为NIC名称,值为未配对的掉线事件
$pendingDrops = @{}

# 确保日志存储目录存在
function Ensure-LogFolder {
    $folderPath = "C:\NIC_Drops"
    if (-not (Test-Path -Path $folderPath)) {
        New-Item -Path $folderPath -ItemType Directory | Out-Null
    }
    return $folderPath
}

# 生成符合要求的CSV文件路径
function Get-CsvFilePath {
    $folder = Ensure-LogFolder
    $fileName = "$($env:COMPUTERNAME)-$($startMonitoringTime.ToString('yyyy-MM-dd_HH-mm-ss')).csv"
    return Join-Path -Path $folder -ChildPath $fileName
}

# 保存日志到CSV(支持增量写入)
function Save-NicLog {
    param(
        [Parameter(Mandatory=$true)]
        [string]$Path,
        [Parameter(Mandatory=$true)]
        [array]$Entries
    )
    # 首次创建时写入表头
    if (-not (Test-Path -Path $Path)) {
        @() | Select-Object NIC, "Event ID", Description, "Time dropped", "Time Established" |
            Export-Csv -Path $Path -NoTypeInformation -Encoding UTF8
    }
    # 写入数据
    if ($Entries.Count -gt 0) {
        $Entries | Export-Csv -Path $Path -NoTypeInformation -Encoding UTF8 -Append
    }
}

# 解析系统事件日志中的网卡事件
function Parse-NicEvent {
    param(
        [Parameter(Mandatory=$true)]
        [System.Diagnostics.Eventing.Reader.EventLogRecord]$Event
    )
    $nicName = $null
    $eventId = $Event.Id
    $description = $null
    $eventTime = $Event.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')

    switch ($Event.LogName) {
        "System" {
            switch ($eventId) {
                27 { # 链路状态下降
                    $nicName = $Event.Properties[0].Value
                    $description = "Link State Down"
                    return [PSCustomObject]@{
                        NIC = $nicName
                        EventId = $eventId
                        Description = $description
                        EventTime = $eventTime
                        EventType = "Drop"
                    }
                }
                28 { # 链路状态恢复
                    $nicName = $Event.Properties[0].Value
                    $description = "Link State Up"
                    return [PSCustomObject]@{
                        NIC = $nicName
                        EventId = $eventId
                        Description = $description
                        EventTime = $eventTime
                        EventType = "Recover"
                    }
                }
                10400 { # TCPIP网卡重置
                    $nicName = $Event.Properties[1].Value
                    $description = "Resetting"
                    return [PSCustomObject]@{
                        NIC = $nicName
                        EventId = $eventId
                        Description = $description
                        EventTime = $eventTime
                        EventType = "Drop"
                    }
                }
                default { return $null }
            }
        }
        default { return $null }
    }
}

# ------------------------------
# 构建GUI界面
# ------------------------------
$form = New-Object System.Windows.Forms.Form
$form.Text = "NIC Drop Monitor"
$form.Size = New-Object System.Drawing.Size(520, 380)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedSingle
$form.MaximizeBox = $false

# 状态提示标签
$statusLabel = New-Object System.Windows.Forms.Label
$statusLabel.Text = "当前状态:未监控"
$statusLabel.AutoSize = $true
$statusLabel.Location = New-Object System.Drawing.Point(12, 15)
$form.Controls.Add($statusLabel)

# 实时事件列表框
$eventList = New-Object System.Windows.Forms.ListBox
$eventList.Location = New-Object System.Drawing.Point(12, 50)
$eventList.Size = New-Object System.Drawing.Size(480, 240)
$eventList.Font = New-Object System.Drawing.Font("Consolas", 9)
$form.Controls.Add($eventList)

# 开始监控按钮
$startBtn = New-Object System.Windows.Forms.Button
$startBtn.Text = "Start Monitoring"
$startBtn.Size = New-Object System.Drawing.Size(140, 30)
$startBtn.Location = New-Object System.Drawing.Point(12, 300)
$startBtn.Add_Click({
    if ($monitoring) { return }

    # 初始化监控状态
    $monitoring = $true
    $startMonitoringTime = Get-Date
    $csvPath = Get-CsvFilePath
    $pendingDrops.Clear()
    $logEntries.Clear()
    $eventList.Items.Clear()

    # 更新UI
    $statusLabel.Text = "当前状态:监控中(日志文件:$($csvPath | Split-Path -Leaf))"
    $startBtn.Enabled = $false
    $cancelBtn.Enabled = $true
    $eventList.Items.Add("[$($startMonitoringTime.ToString('yyyy-MM-dd HH:mm:ss'))] 开始监听网卡事件...")

    # ------------------------------
    # 启动系统事件日志监听
    # ------------------------------
    $eventQuery = @"
<QueryList>
  <Query Id="0" Path="System">
    <Select Path="System">*[System[(EventID=27 or EventID=28 or EventID=10400)]]</Select>
  </Query>
</QueryList>
"@
    $eventWatcher = New-Object System.Diagnostics.Eventing.Reader.EventLogWatcher($eventQuery)
    $eventWatcher.Enabled = $true

    # 事件触发处理逻辑
    $eventWatcher.Add_EventRecordWritten({
        param($sender, $e)
        if (-not $monitoring) {
            $sender.Enabled = $false
            return
        }

        $parsedEvent = Parse-NicEvent -Event $e.EventRecord
        if (-not $parsedEvent) { return }

        switch ($parsedEvent.EventType) {
            "Drop" {
                # 记录未配对的掉线事件
                $pendingDrops[$parsedEvent.NIC] = $parsedEvent
                $eventList.Items.Add("[$($parsedEvent.EventTime)] 【掉线】$($parsedEvent.NIC) - $($parsedEvent.Description) (Event ID: $($parsedEvent.EventId))")
            }
            "Recover" {
                # 配对掉线与恢复事件
                if ($pendingDrops.ContainsKey($parsedEvent.NIC)) {
                    $dropEvent = $pendingDrops[$parsedEvent.NIC]
                    # 生成符合需求的日志条目
                    $logEntry = [PSCustomObject]@{
                        NIC = $dropEvent.NIC
                        "Event ID" = $dropEvent.EventId
                        Description = $dropEvent.Description
                        "Time dropped" = $dropEvent.EventTime
                        "Time Established" = $parsedEvent.EventTime
                    }
                    $logEntries += $logEntry
                    # 增量写入CSV
                    Save-NicLog -Path $csvPath -Entries @($logEntry)
                    # 移除已配对的事件
                    $pendingDrops.Remove($parsedEvent.NIC)
                    $eventList.Items.Add("[$($parsedEvent.EventTime)] 【恢复】$($parsedEvent.NIC) - 已配对掉线事件,写入日志")
                } else {
                    $eventList.Items.Add("[$($parsedEvent.EventTime)] 【恢复】$($parsedEvent.NIC) - 无对应掉线事件,忽略")
                }
            }
        }
    })
})

# 取消监控按钮
$cancelBtn = New-Object System.Windows.Forms.Button
$cancelBtn.Text = "Cancel"
$cancelBtn.Size = New-Object System.Drawing.Size(140, 30)
$cancelBtn.Location = New-Object System.Drawing.Point(170, 300)
$cancelBtn.Enabled = $false
$cancelBtn.Add_Click({
    if (-not $monitoring) { return }

    $monitoring = $false
    # 停止事件监听
    if ($eventWatcher) { $eventWatcher.Enabled = $false }
    # 保存未恢复的掉线事件
    foreach ($nic in $pendingDrops.Keys) {
        $dropEvent = $pendingDrops[$nic]
        $logEntry = [PSCustomObject]@{
            NIC = $dropEvent.NIC
            "Event ID" = $dropEvent.EventId
            Description = $dropEvent.Description
            "Time dropped" = $dropEvent.EventTime
            "Time Established" = "未恢复"
        }
        $logEntries += $logEntry
        Save-NicLog -Path $csvPath -Entries @($logEntry)
    }
    # 更新UI
    $statusLabel.Text = "当前状态:已停止监控,日志已保存"
    $startBtn.Enabled = $true
    $cancelBtn.Enabled = $false
    $eventList.Items.Add("[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] 监控已停止,所有日志已保存")
})

$form.Controls.Add($startBtn)
$form.Controls.Add($cancelBtn)

# 关闭窗体时自动触发Cancel逻辑
$form.Add_FormClosing({
    if ($monitoring) {
        $cancelBtn.PerformClick()
    }
})

# 显示窗体
$form.Add_Shown({ $form.Activate() })
[void]$form.ShowDialog()

关键改进点说明

  1. 从轮询改为系统事件监听

    • 直接监听Windows系统日志中网卡相关的官方Event ID,确保不会错过任何状态变更
    • 支持你需求中的Event ID 27(链路下降)、10400(重置),以及对应的恢复事件ID 28
  2. 实现掉线-恢复事件配对

    • 用字典存储未配对的掉线事件,捕获到恢复事件时自动关联为同一CSV行
    • 点击Cancel时,未恢复的掉线事件会被标记为「未恢复」并写入日志
  3. 严格符合CSV格式需求

    • CSV字段完全匹配你的要求:NICEvent IDDescriptionTime droppedTime Established
    • 日志文件命名规则为计算机名-监控开始时间.csv,自动存储到C:\NIC_Drops
    • 每一条完整的掉线-恢复事件单独占一行,逗号分隔
  4. UI与稳定性优化

    • 实时在列表框中显示监控状态和事件详情
    • 关闭窗体时自动保存日志,避免数据丢失
    • 禁用最大化按钮,固定窗体尺寸提升操作体验

常见问题排查

如果仍出现事件不捕获的情况,可尝试以下操作:

  1. 以管理员身份运行PowerShell——监听系统事件日志需要管理员权限
  2. 手动验证事件日志:打开「事件查看器」→「Windows日志」→「系统」,过滤Event ID 27/28/10400,确认断开/连接网卡时是否有事件生成
  3. 自定义Event ID支持:如果你的网卡驱动使用自定义Event ID,可修改Parse-NicEvent函数中的switch case,添加对应的Event ID解析逻辑

火山引擎 最新活动