Este artículo es una traducción al español, hecha con ayuda de IA, de mi post original en inglés: Wi-Fi keeps dropping on Windows 11? Find out where with PowerShell before you reset anything. El código, los comandos y los números se cotejaron con el original y con el repositorio antes de publicar.
Cuando el Wi-Fi se corta una y otra vez, el consejo habitual es una lista de cosas que restablecer: olvidar la red, reiniciar el router, netsh winsock reset, reinstalar el controlador, Restablecimiento de red. La propia página de Microsoft "Fix Wi-Fi connection issues in Windows" los enumera más o menos en ese orden, y es una buena lista. Empieza por el solucionador de problemas de red e Internet de la aplicación Obtener ayuda.
Pero la guía de solución de problemas de Microsoft para profesionales de TI hace una observación que es fácil pasar por alto: hay que recopilar una línea base y anotar cuándo ocurre el problema antes de restablecer la pila de red, borrar perfiles inalámbricos o quitar el controlador. Y hay una pregunta que ningún restablecimiento responde: ¿el corte está entre tu PC y tu router, o más allá del router? Si está más allá del router, por mucho que restablezcas tu PC no se va a arreglar.
En este post construimos un pequeño archivo de PowerShell, de solo lectura, que responde esa pregunta en cuatro partes:
-
Qué dice el adaptador ahora mismo (
netsh wlan show interfaces, como objetos). - Qué registró Windows cuando se cortó (eventos de desconexión con el texto del motivo).
- Si Windows puede apagar el adaptador para ahorrar batería.
- Un registro de pings a tu router y, opcionalmente, a una dirección de Internet, con un resumen que te dice dónde están los cortes.
Nada de esto cambia ninguna configuración. El único archivo que escribe es el registro CSV cuyo nombre tú eliges.
1. El enlace, como objetos
netsh wlan show interfaces imprime un bloque Key : Value por cada interfaz Wi-Fi: estado, SSID, el BSSID del punto de acceso, banda, canal, tipo de radio, velocidades de recepción y transmisión, y la señal como porcentaje. Se lee bien, pero es incómodo de registrar. Un pequeño parser lo convierte en 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') { # a new interface starts
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 }
}
Tres detalles que conviene saber:
-
Hay dos etiquetas posibles para el punto de acceso. Según la compilación de Windows, la línea puede decir
AP BSSIDo soloBSSID. El mapa acepta ambas. -
La expresión regular divide en el primer
:(espacio, dos puntos), así que un BSSID comoaa:bb:cc:dd:ee:01queda intacto. -
Las etiquetas pueden estar traducidas. Con un idioma para mostrar que no sea inglés, las claves pueden no coincidir y entonces no obtienes nada.
Get-WifiStatus(el envoltorio que realmente ejecutanetsh) muestra una advertencia cuando eso pasa, en lugar de fallar en silencio.
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. Qué registró Windows cuando se cortó
El servicio WLAN AutoConfig tiene su propio registro de eventos: Registros de aplicaciones y servicios > Microsoft > Windows > WLAN-AutoConfig > Operational. El evento 8001 es una conexión correcta y el 8003 es una desconexión. Cada 8003 incluye el SSID, el adaptador y, lo más útil, una cadena Reason y un ReasonCode.
El texto del motivo está en el XML del evento, no en su mensaje resumido, así que leemos los campos de EventData por nombre:
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
}
}
-FilterHashtable filtra dentro del servicio de registro de eventos, lo que es mucho más rápido que pasar todo el registro por Where-Object. Aquí -ErrorAction SilentlyContinue importa: Get-WinEvent lanza "No events were found" cuando el filtro no coincide con nada, y una semana sin cortes es una respuesta válida.
Get-WifiDisconnectEvent -Days 7 | Format-Table TimeCreated, SSID, Reason -AutoSize
Agrupa por Reason para ver si los cortes comparten una causa. Un motivo que dice que el controlador informó la desconexión apunta al lado del adaptador; un motivo que dice que tú o una directiva desconectaron apunta a otro lugar completamente distinto.
Una advertencia: cuando un PC entra en suspensión y se reactiva dentro del alcance de la misma red, puede que Windows no registre un par 8003/8001, así que una lista corta no demuestra que no hubo cortes. Si quieres el panorama completo, netsh wlan show wlanreport (ejecutado como administrador) genera un informe HTML que, en palabras de Microsoft, resume las sesiones inalámbricas recientes, los motivos de desconexión, la información del adaptador y los eventos relacionados.
3. ¿Puede Windows apagar el adaptador?
"El Wi-Fi se corta cuando el PC lleva un rato inactivo o después de la suspensión" suele deberse a la opción de ahorro de energía del adaptador: Administrador de dispositivos > tu adaptador Wi-Fi > Propiedades > Administración de energía > Permitir que el equipo apague este dispositivo para ahorrar energía. Puedes leer la misma opción con Get-NetAdapterPowerManagement, que necesita una ventana con privilegios 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
}
}
}
Esto solo informa. Si decides desmarcar la casilla, hazlo en el Administrador de dispositivos, donde volver a marcarla es la forma de deshacerlo.
4. ¿Router o Internet? Regístralo
Esta parte te da un registro con marcas de tiempo para mostrárselo a tu proveedor de Internet. Cada pocos segundos, haz ping a tu puerta de enlace predeterminada (tu router) y, opcionalmente, a una dirección pública. Escribe cada muestra en un CSV junto con la señal y el BSSID:
- El router deja de responder → el problema está entre tu PC y tu router (señal, ahorro de energía, controlador o el router).
- El router responde, la dirección de Internet no → tu Wi-Fi está bien; el problema está más allá del router.
El ping usa directamente System.Net.NetworkInformation.Ping de .NET, que debería comportarse igual en Windows PowerShell 5.1 y en PowerShell 7 (todavía no se ha comprobado en 5.1; Test-Connection cambió sus parámetros y su salida entre las dos versiones):
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() }
}
La puerta de enlace se obtiene de la ruta predeterminada con la métrica más baja:
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 }
}
La función de registro agrega una fila por muestra con Export-Csv -Append, así que si la detienes con Ctrl+C, todo lo registrado hasta ese momento ya está en 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 }
}
}
Después, Measure-WifiLog vuelve a leer el CSV y cuenta los fallos del router, las muestras de "router bien, Internet falló", la señal más baja y cuántas veces cambió el BSSID —un cambio de punto de acceso o de banda (roaming)—, e imprime un veredicto de una línea. Está en el repositorio; es simplemente contar sobre Import-Csv.
Ejecútalo mientras el problema está ocurriendo:
. .\wifi-drop-diagnostic.ps1
Watch-WifiConnection -Minutes 30 -InternetHost 1.1.1.1 -Path .\wifi-log.csv
Measure-WifiLog -Path .\wifi-log.csv
1.1.1.1 es solo un ejemplo de dirección pública que responde a pings. Si omites -InternetHost, solo se hace ping a tu router.
Trampas a tener en cuenta
-
Algunos routers ignoran los pings. Si
GatewayOkes false incluso cuando todo funciona, la función de registro no puede ayudarte en esa red. -
Con un ping cada pocos segundos, puede que no se detecte un corte muy breve. Baja
-IntervalSecondssi necesitas más detalle; el valor predeterminado de 5 mantiene el registro pequeño. -
Más de un adaptador Wi-Fi: la función de registro anota el primero que lista
netsh. -
Windows en otro idioma que no sea inglés:
Get-WifiStatuspuede no devolver nada y mostrar una advertencia. Los pings y el lector del registro de eventos deberían seguir funcionando, porque no leen texto de la interfaz, pero eso no se ha probado.
Cómo se probó (y qué no)
El script se analiza sin errores, PSScriptAnalyzer no reporta ningún hallazgo (incluida su regla de compatibilidad de sintaxis con Windows PowerShell 5.1) y pasan 12 pruebas de Pester. Esas pruebas se ejecutaron con PowerShell 7 en Linux, con texto de ejemplo escrito a mano en el formato de netsh, un XML de evento de ejemplo y mocks para el registro de eventos, el ping y la pausa. Todavía no se ha ejecutado en un PC real con Windows 10/11 ni en Windows PowerShell 5.1. Como no cambia nada, lo peor que puede pasar es un informe incorrecto o vacío, pero léelo antes de ejecutarlo.
Páginas de Microsoft consultadas: "Fix Wi-Fi connection issues in Windows" (Soporte técnico de Microsoft), "Wireless network connectivity issues troubleshooting" (Microsoft Learn) y la referencia de Get-NetAdapterPowerManagement (Microsoft Learn). Los enlaces están en el README del repositorio.
Mi repositorio, con el script completo, las pruebas y un README (gratis, MIT): https://github.com/fecklesspleb/wifi-drop-diagnostic
Sobre el uso de IA: este artículo se tradujo del inglés con ayuda de IA; el artículo original y el código también se escribieron con ayuda de IA. El código, los comandos y los números se cotejaron con el original y con el repositorio antes de publicar. Sin afiliación con Microsoft ni respaldo de su parte.
Top comments (0)