PowerShell网卡连接中断监控GUI脚本开发及事件捕获问题求助
PowerShell网卡连接中断监控GUI脚本开发及事件捕获问题求助
问题分析
你当前的脚本采用定时轮询网卡状态的方式,但这种方式存在两个核心问题:
- 轮询间隔(5秒)可能错过短暂的状态变化,或者某些网卡状态变更不会被
Get-NetAdapter及时捕获 - 没有实现你需求中的「掉线-恢复事件配对」逻辑(当前脚本只记录单次状态变更,无法将掉线和恢复关联为同一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 Monitoring和Cancel按钮的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()
关键改进点说明
从轮询改为系统事件监听
- 直接监听Windows系统日志中网卡相关的官方Event ID,确保不会错过任何状态变更
- 支持你需求中的Event ID 27(链路下降)、10400(重置),以及对应的恢复事件ID 28
实现掉线-恢复事件配对
- 用字典存储未配对的掉线事件,捕获到恢复事件时自动关联为同一CSV行
- 点击Cancel时,未恢复的掉线事件会被标记为「未恢复」并写入日志
严格符合CSV格式需求
- CSV字段完全匹配你的要求:
NIC、Event ID、Description、Time dropped、Time Established - 日志文件命名规则为
计算机名-监控开始时间.csv,自动存储到C:\NIC_Drops - 每一条完整的掉线-恢复事件单独占一行,逗号分隔
- CSV字段完全匹配你的要求:
UI与稳定性优化
- 实时在列表框中显示监控状态和事件详情
- 关闭窗体时自动保存日志,避免数据丢失
- 禁用最大化按钮,固定窗体尺寸提升操作体验
常见问题排查
如果仍出现事件不捕获的情况,可尝试以下操作:
- 以管理员身份运行PowerShell——监听系统事件日志需要管理员权限
- 手动验证事件日志:打开「事件查看器」→「Windows日志」→「系统」,过滤Event ID 27/28/10400,确认断开/连接网卡时是否有事件生成
- 自定义Event ID支持:如果你的网卡驱动使用自定义Event ID,可修改
Parse-NicEvent函数中的switch case,添加对应的Event ID解析逻辑




