Este artigo é uma tradução para o português, feita com ajuda de IA, do meu post original em inglês: Wi-Fi keeps dropping on Windows 11? Find out where with PowerShell before you reset anything. O código, os comandos e os números foram conferidos com o original e com o repositório antes da publicação.
Quando o Wi-Fi vive caindo, o conselho de sempre é uma lista de resets: esquecer a rede, reiniciar o roteador, netsh winsock reset, reinstalar o driver, redefinição de rede. A própria página da Microsoft "Fix Wi-Fi connection issues in Windows" traz esses passos mais ou menos nessa ordem, e é uma boa lista. Comece pela solução de problemas de Rede e Internet no app Obter Ajuda.
Só que o guia de solução de problemas da Microsoft para profissionais de TI faz uma observação fácil de pular: colete uma linha de base e anote quando o problema acontece antes de resetar a pilha de rede, apagar perfis de rede sem fio ou remover o driver. E existe uma pergunta que nenhum reset responde: a queda acontece entre o seu PC e o roteador, ou depois do roteador? Se for depois do roteador, não adianta resetar o PC quantas vezes for.
Neste post, vamos montar um pequeno arquivo PowerShell, somente leitura, que responde a isso em quatro partes:
-
O que o adaptador informa agora (
netsh wlan show interfaces, como objetos). - O que o Windows registrou quando a conexão caiu (eventos de desconexão com o texto do motivo).
- Se o Windows pode desligar o adaptador para economizar bateria.
- Um log de pings para o roteador e, opcionalmente, para um endereço na internet, com um resumo que mostra onde estão as quedas.
Nada disso altera configuração alguma. O único arquivo que ele grava é o log CSV que você indicar.
1. O link, como objetos
O netsh wlan show interfaces imprime um bloco Key : Value para cada interface Wi-Fi: estado, SSID, o BSSID do ponto de acesso, banda, canal, tipo de rádio, taxas de recepção e transmissão e o sinal em porcentagem. Dá para ler numa boa, mas é chato de registrar em log. Um pequeno parser transforma isso em objetos:
function ConvertFrom-WlanInterfaceText {
[CmdletBinding()]
param([Parameter(Mandatory)][AllowEmptyString()][string[]] $Text)
$map = [ordered]@{
'Name' = 'Name'; 'Description' = 'Description'; 'State' = 'State'
'SSID' = 'SSID'; 'AP BSSID' = 'BSSID'; 'BSSID' = 'BSSID'
'Radio type' = 'RadioType'; 'Authentication' = 'Authentication'
'Band' = 'Band'; 'Channel' = 'Channel'
'Receive rate (Mbps)' = 'ReceiveMbps'; 'Transmit rate (Mbps)' = 'TransmitMbps'
'Signal' = 'SignalPercent'; 'Profile' = 'Profile'
}
$current = $null
foreach ($line in ($Text -split "`r?`n")) {
if ($line -notmatch '^\s+(?<key>[^:]+?)\s+:\s?(?<value>.*)$') { continue }
$key = $Matches.key.Trim()
$value = $Matches.value.Trim()
if ($key -eq 'Name') { # começa uma nova interface
if ($current) { [pscustomobject]$current }
$current = [ordered]@{}
foreach ($p in ($map.Values | Select-Object -Unique)) { $current[$p] = $null }
}
if ($null -eq $current -or -not $map.Contains($key)) { continue }
$prop = $map[$key]
switch ($prop) {
'SignalPercent' { $current[$prop] = [int]($value -replace '[^\d]', '') }
{ $_ -in 'Channel', 'ReceiveMbps', 'TransmitMbps' } {
$n = 0.0
if ([double]::TryParse($value, [Globalization.NumberStyles]::Float,
[Globalization.CultureInfo]::InvariantCulture, [ref]$n)) { $current[$prop] = $n }
}
default { $current[$prop] = $value }
}
}
if ($current) { [pscustomobject]$current }
}
Três detalhes que vale conhecer:
-
Dois rótulos possíveis para o ponto de acesso. Dependendo da build do Windows, a linha pode ser
AP BSSIDou sóBSSID. O mapa aceita os dois. -
A regex separa no primeiro
:(espaço, dois-pontos), então um BSSID comoaa:bb:cc:dd:ee:01sobrevive inteiro. -
Os rótulos podem estar traduzidos. Com o Windows em outro idioma de exibição que não o inglês, as chaves podem não bater, e aí não volta nada. O
Get-WifiStatus(o wrapper que de fato executa onetsh) emite um aviso quando isso acontece, em vez de falhar em silêncio.
function Get-WifiStatus {
[CmdletBinding()]
param()
if (-not (Get-Command -Name netsh.exe -ErrorAction SilentlyContinue)) {
throw 'netsh.exe was not found. This function needs Windows.'
}
$raw = & netsh.exe wlan show interfaces
$found = 0
foreach ($interface in (ConvertFrom-WlanInterfaceText -Text $raw)) { $found++; $interface }
if ($found -eq 0) {
Write-Warning 'No Wi-Fi interface was found in the netsh output. If your Windows display language is not English, run "netsh wlan show interfaces" and read it directly.'
}
}
2. O que o Windows registrou quando a conexão caiu
O serviço WLAN AutoConfig mantém seu próprio log de eventos: Logs de Aplicativos e Serviços > Microsoft > Windows > WLAN-AutoConfig > Operational. O evento 8001 é uma conexão bem-sucedida e o 8003 é uma desconexão. Cada 8003 traz o SSID, o adaptador e, o mais útil, uma string Reason e um ReasonCode.
O texto do motivo fica no XML do evento, não na mensagem de resumo, então leia os campos de EventData pelo nome:
function ConvertFrom-WlanEventXml {
[CmdletBinding()]
param([Parameter(Mandatory)][string] $Xml, [datetime] $TimeCreated, [int] $Id)
$doc = [xml]$Xml
$data = @{}
foreach ($d in $doc.Event.EventData.Data) { $data[[string]$d.Name] = [string]$d.'#text' }
[pscustomobject]@{
TimeCreated = $TimeCreated
Id = $Id
SSID = $data['SSID']
Adapter = $data['InterfaceDescription']
Reason = $data['Reason']
ReasonCode = $data['ReasonCode']
}
}
function Get-WifiDisconnectEvent {
[CmdletBinding()]
param([ValidateRange(1, 90)][int] $Days = 7)
$filter = @{
LogName = 'Microsoft-Windows-WLAN-AutoConfig/Operational'
Id = 8003
StartTime = (Get-Date).AddDays(-$Days)
}
$events = @(Get-WinEvent -FilterHashtable $filter -ErrorAction SilentlyContinue)
foreach ($e in $events) {
ConvertFrom-WlanEventXml -Xml $e.ToXml() -TimeCreated $e.TimeCreated -Id $e.Id
}
}
O -FilterHashtable filtra dentro do próprio serviço de log de eventos, o que é bem mais rápido do que passar o log inteiro por um Where-Object. O -ErrorAction SilentlyContinue é importante aqui: o Get-WinEvent lança "No events were found" quando o filtro não encontra nada, e uma semana sem quedas é uma resposta válida.
Get-WifiDisconnectEvent -Days 7 | Format-Table TimeCreated, SSID, Reason -AutoSize
Agrupe por Reason para ver se as quedas têm uma causa em comum. Um motivo dizendo que o driver relatou a desconexão aponta para o lado do adaptador; um motivo dizendo que você ou uma política desconectou aponta para outro lugar completamente diferente.
Uma ressalva: quando o PC entra em suspensão e acorda no alcance da mesma rede, o Windows pode não registrar um par 8003/8001, então uma lista curta não prova que não houve quedas. Se quiser o quadro completo, o netsh wlan show wlanreport (executado como administrador) gera um relatório HTML que, nas palavras da Microsoft, resume as sessões sem fio recentes, os motivos de desconexão, as informações do adaptador e os eventos relacionados.
3. O Windows pode desligar o adaptador?
"O Wi-Fi cai depois que o PC fica ocioso ou depois da suspensão" muitas vezes é culpa da configuração de economia de energia do adaptador: Gerenciador de Dispositivos > seu adaptador Wi-Fi > Propriedades > Gerenciamento de Energia > Permitir que o computador desligue este dispositivo para economizar energia. Dá para ler a mesma configuração com o Get-NetAdapterPowerManagement, que precisa de uma janela com privilégios de administrador:
function Get-WifiAdapterPowerSetting {
[CmdletBinding()]
param()
$wifi = @(Get-NetAdapter -Physical -ErrorAction Stop |
Where-Object { $_.PhysicalMediaType -eq 'Native 802.11' })
foreach ($a in $wifi) {
$pm = Get-NetAdapterPowerManagement -Name $a.Name -ErrorAction SilentlyContinue
$allow = if ($pm) { [string]$pm.AllowComputerToTurnOffDevice } else { 'Not reported' }
[pscustomobject]@{
Name = $a.Name
Description = $a.InterfaceDescription
Status = [string]$a.Status
AllowComputerToTurnOffDevice = $allow
}
}
}
Isso só mostra a configuração. Se decidir desmarcar a opção, faça isso no Gerenciador de Dispositivos, onde basta marcá-la de novo para desfazer.
4. Roteador ou internet? Registre em log
Esta parte gera um log com data e hora para você mostrar ao seu provedor de internet. A cada poucos segundos, faça ping no seu gateway padrão (o roteador) e, opcionalmente, num endereço público. Grave cada amostra num CSV junto com o sinal e o BSSID:
- O roteador para de responder → o problema está entre o seu PC e o roteador (sinal, economia de energia, driver ou o próprio roteador).
- O roteador responde, mas o endereço na internet não → seu Wi-Fi está bem; o problema está depois do roteador.
O ping usa diretamente o System.Net.NetworkInformation.Ping do .NET, que deve se comportar igual no Windows PowerShell 5.1 e no PowerShell 7 (ainda não verificado no 5.1; o Test-Connection mudou parâmetros e saída entre as duas versões):
function Test-PingOnce {
[CmdletBinding()]
[OutputType([bool])]
param([Parameter(Mandatory)][string] $Target, [int] $TimeoutMs = 1000)
$ping = New-Object System.Net.NetworkInformation.Ping
try { return ($ping.Send($Target, $TimeoutMs).Status -eq 'Success') }
catch { return $false }
finally { $ping.Dispose() }
}
O gateway vem da rota padrão com a menor métrica:
function Get-DefaultGateway {
[CmdletBinding()]
param()
$route = Get-NetRoute -DestinationPrefix '0.0.0.0/0' -ErrorAction SilentlyContinue |
Sort-Object -Property RouteMetric | Select-Object -First 1
if ($route) { $route.NextHop }
}
O logger acrescenta uma linha por amostra com Export-Csv -Append, então, se você interromper com Ctrl+C, tudo o que já foi registrado está salvo no disco:
function Watch-WifiConnection {
[CmdletBinding()]
param(
[Parameter(Mandatory)][string] $Path,
[ValidateRange(1, 1440)][int] $Minutes = 30,
[ValidateRange(1, 300)][int] $IntervalSeconds = 5,
[int] $Samples = 0,
[string] $Gateway,
[string] $InternetHost
)
if (-not $Gateway) { $Gateway = Get-DefaultGateway }
if (-not $Gateway) { throw 'No default gateway found. Connect to Wi-Fi first, or pass -Gateway.' }
if ($Samples -le 0) { $Samples = [math]::Max(1, [int](($Minutes * 60) / $IntervalSeconds)) }
$haveNetsh = [bool](Get-Command -Name netsh.exe -ErrorAction SilentlyContinue)
for ($i = 0; $i -lt $Samples; $i++) {
$wifi = $null
if ($haveNetsh) { $wifi = Get-WifiStatus -WarningAction SilentlyContinue | Select-Object -First 1 }
$row = [pscustomobject]@{
Time = (Get-Date).ToString('yyyy-MM-ddTHH:mm:ss')
State = if ($wifi) { $wifi.State } else { '' }
SSID = if ($wifi) { $wifi.SSID } else { '' }
BSSID = if ($wifi) { $wifi.BSSID } else { '' }
Signal = if ($wifi) { $wifi.SignalPercent } else { '' }
GatewayOk = Test-PingOnce -Target $Gateway
InternetOk = if ($InternetHost) { Test-PingOnce -Target $InternetHost } else { '' }
}
$row | Export-Csv -LiteralPath $Path -Append -NoTypeInformation -Encoding UTF8
$row
if ($i -lt $Samples - 1) { Start-Sleep -Seconds $IntervalSeconds }
}
}
Depois, o Measure-WifiLog lê o CSV de volta e conta as falhas do roteador, as amostras com "roteador ok, internet falhou", o sinal mais baixo e quantas vezes o BSSID mudou (um roaming para outro ponto de acesso ou outra banda), e imprime um veredito em uma linha. Ele está no repositório; é só contagem simples em cima do Import-Csv.
Rode enquanto o problema está acontecendo:
. .\wifi-drop-diagnostic.ps1
Watch-WifiConnection -Minutes 30 -InternetHost 1.1.1.1 -Path .\wifi-log.csv
Measure-WifiLog -Path .\wifi-log.csv
O 1.1.1.1 é só um exemplo de endereço público que responde a ping. Se deixar o -InternetHost de fora, só o roteador recebe ping.
Pegadinhas
-
Alguns roteadores ignoram ping. Se
GatewayOkder false mesmo com tudo funcionando, o logger não ajuda nessa rede. -
Um ping a cada poucos segundos pode deixar passar uma queda muito curta. Diminua o
-IntervalSecondsse precisar de mais detalhe; o padrão de 5 mantém o log pequeno. -
Mais de um adaptador Wi-Fi: o logger registra o primeiro que o
netshlistar. -
Windows em outro idioma: o
Get-WifiStatuspode não devolver nada e emitir um aviso. Os pings e a leitura do log de eventos devem continuar funcionando, porque não leem texto de exibição, mas isso não foi testado.
Como foi testado (e o que não foi)
O script faz parsing sem erros, o PSScriptAnalyzer não aponta nenhum problema (incluindo a regra de compatibilidade de sintaxe com o Windows PowerShell 5.1) e 12 testes Pester passam. Esses testes rodaram no PowerShell 7 em Linux, com texto de exemplo escrito à mão no formato do netsh, um XML de evento de exemplo e mocks para o log de eventos, o ping e o sleep. Ainda não foi executado num PC real com Windows 10/11 nem no Windows PowerShell 5.1. Como ele não altera nada, o pior caso é um relatório errado ou vazio, mas leia o script antes de rodar.
Páginas da Microsoft usadas: "Fix Wi-Fi connection issues in Windows" (Suporte da Microsoft), "Wireless network connectivity issues troubleshooting" (Microsoft Learn) e a referência do Get-NetAdapterPowerManagement (Microsoft Learn). Os links estão no README do repositório.
Meu repositório, com o script completo, os testes e um README (gratuito, MIT): https://github.com/fecklesspleb/wifi-drop-diagnostic
Sobre o uso de IA: este artigo foi traduzido do inglês com ajuda de IA; o artigo original e o código também foram escritos com ajuda de IA. O código, os comandos e os números foram conferidos com o original e com o repositório antes da publicação. Sem afiliação com a Microsoft nem endosso dela.
Top comments (0)