DEV Community

fecklesspleb
fecklesspleb

Posted on

O Wi-Fi do Windows 11 vive caindo? Descubra onde com PowerShell antes de resetar qualquer coisa

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:

  1. O que o adaptador informa agora (netsh wlan show interfaces, como objetos).
  2. O que o Windows registrou quando a conexão caiu (eventos de desconexão com o texto do motivo).
  3. Se o Windows pode desligar o adaptador para economizar bateria.
  4. 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 }
}
Enter fullscreen mode Exit fullscreen mode

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 BSSID ou só BSSID. O mapa aceita os dois.
  • A regex separa no primeiro : (espaço, dois-pontos), então um BSSID como aa:bb:cc:dd:ee:01 sobrevive 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 o netsh) 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.'
    }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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() }
}
Enter fullscreen mode Exit fullscreen mode

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 }
}
Enter fullscreen mode Exit fullscreen mode

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 }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 GatewayOk der 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 -IntervalSeconds se 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 netsh listar.
  • Windows em outro idioma: o Get-WifiStatus pode 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)