Diagnosticar problemas de Windows Update puede ser una tarea tediosa para los profesionales de TI, especialmente cuando se gestionan varios equipos. Los retrasos o errores en el proceso de actualización pueden provocar vulnerabilidades de seguridad, problemas de conformidad e inestabilidad general del sistema.
El script PowerShell proporcionado ofrece un enfoque optimizado para diagnosticar problemas de Windows Update y solucionarlos, garantizando que los sistemas permanezcan actualizados y seguros. Este post profundiza en la funcionalidad del script, explorando cómo puede ser una herramienta inestimable tanto para los profesionales de TI como para los proveedores de servicios gestionados (MSP).
Contexto
Windows Update es un componente crítico en el mantenimiento del buen estado y la seguridad de un sistema basado en Windows. Sin embargo, diversos factores pueden dificultar su buen funcionamiento, desde errores de configuración del servicio hasta problemas de red. Los profesionales de TI a menudo se enfrentan a la desalentadora tarea de solucionar estos problemas manualmente, lo que puede llevar mucho tiempo y ser propenso a errores humanos.
El script PowerShell proporcionado aborda este reto automatizando el proceso de diagnóstico, garantizando que los problemas comunes se identifiquen y resuelvan rápidamente. Con este script para diagnosticar problemas de Windows Update, los equipos de TI pueden mantener la integridad del sistema, reducir el tiempo de inactividad y mejorar la eficacia general.
El script para diagnosticar problemas de Windows Update
#Requires -Version 5.1 <# .SYNOPSIS Diagnose Windows Update issues. .DESCRIPTION Checks that CryptSvc, and bits or running or not Checks that wuauserv is running and the startup type is set correctly. Checks WaaSMedic plugins doesn't have issues. (Only applies to OS Build Version is greater than 17600). Checks if NTP is setup. Checks Windows Update logs for any errors in the last week. .EXAMPLE (No Parameters) ## EXAMPLE OUTPUT WITHOUT PARAMS ## [Info] Last checked for updates on 4/29/2023 [Issue] Windows Update has not checked for updates in over 30 days. PARAMETER: -ResultsCustomField WindowsUpdate Saves results to a multi-line custom field. .EXAMPLE -ResultsCustomField WindowsUpdate ## EXAMPLE OUTPUT WITH ResultsCustomField ## [Info] Last checked for updates on 4/29/2023 [Issue] Windows Update has not checked for updates in over 90 days. .OUTPUTS None .NOTES Minimum OS Architecture Supported: Windows 10, Windows Server 2016 Release Notes: Initial Release By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://ninjastage2.wpengine.com/terms-of-use. Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA). #> [CmdletBinding()] param ( [int]$Days = 30, [string]$ResultsCustomField ) begin { if ($env:Days) { $Days = $env:Days } if ($env:resultscustomfield -notlike "null") { $ResultsCustomField = $env:resultscustomfield } function Test-IsElevated { $id = [System.Security.Principal.WindowsIdentity]::GetCurrent() $p = New-Object System.Security.Principal.WindowsPrincipal($id) $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) } function Test-WaaSMedic { [CmdletBinding()] param() $WaaS = 0 Try { $WaaS = New-Object -ComObject "Microsoft.WaaSMedic.1" } Catch { Write-Host "WaaS Medic Support: No" } Try { if ($WaaS -ne 0) { Write-Host "WaaS Medic Support: Yes" $Plugins = $WaaS.LaunchDetectionOnly("Troubleshooter") if ($Plugins -eq "") { [PSCustomObject]@{ Id = "WaaSMedic" Detected = $false Parameter = @{"error" = $Plugins } } } else { [PSCustomObject]@{ Id = "WaaSMedic" Detected = $true Parameter = @{"error" = $Plugins } } "Plugins that might have errors: " + $Plugins | Out-String | Write-Host } } } Catch { Write-Host "WaaS Medic Detection: Failed" } Finally { # Release COM Object if we aren't running test cases if (-not $env:NinjaPesterTesting) { [System.Runtime.Interopservices.Marshal]::ReleaseComObject($WaaS) | Out-Null } } } function Get-TimeSyncType { [string]$result = "" [string]$registryKey = "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters" [string]$registryKeyName = "Type" if ((Test-Path $registryKey -ErrorAction SilentlyContinue)) { $registryEntry = Get-Item -Path $registryKey -ErrorAction SilentlyContinue if ($null -ne $registryEntry) { return Get-ItemPropertyValue -Path $registryKey -Name $registryKeyName } } return $result } function Test-ConnectedToInternet { $NLMType = [Type]::GetTypeFromCLSID('DCB00C01-570F-4A9B-8D69-199FDBA5723B') $INetworkListManager = [Activator]::CreateInstance($NLMType) return ($INetworkListManager.IsConnectedToInternet -eq $true) } function Get-ComponentAndErrorCode([string]$msg) { $Codes = [regex]::matches($msg, "0x[a-f0-9a-f0-9A-F0-9A-F0-9]{6,8}") if ($Codes.count -gt 1) { $CodeList = "" # there can be more than one error code can be returned for the same component at once foreach ($Code in $Codes) { $CodeList += "_" + $Code } return $CodeList } else { return $Codes[0].Value } } function Get-DatedEvents($EventLog) { $DatedEvents = @() if ($null -eq $EventLog) { return $null } foreach ($Event in $EventLog) { #$eventMsg = $event.Message $DatedEvents += $Event.Message } return $DatedEvents } function Get-SystemEvents($EventSrc, $Time) { $Events = Get-WinEvent -ProviderName $EventsSrc -ErrorAction 0 | Where-Object { ($_.LevelDisplayName -ne "Information") -and (($_.Id -eq 20) -or ($_.Id -eq 25)) -and ($_.TimeCreated -gt $Time) } return $Events } function Get-HasWinUpdateErrorInLastWeek([switch]$AllLastWeekError) { $Events = @() $EventsSrc = "Microsoft-Windows-WindowsUpdateClient" $startTime = (Get-Date) - (New-TimeSpan -Day 8) $wuEvents = Get-SystemEvents $EventsSrc $startTime if ($null -eq $wuEvents) { return $null } $Events += Get-DatedEvents $wuEvents $LatestError = Get-ComponentAndErrorCode $Events[0] $ErrorList = @{} $ErrorList.add("latest", $LatestError) if ($AllLastWeekError) { foreach ($str in $Events) { $ECode = Get-ComponentAndErrorCode $str if ($null -ne $ECode -and !$ErrorList.ContainsValue($ECode)) { $ErrorList.add($ECode, $ECode) } } } return $ErrorList } Function Get-LocalTime($UTCTime) { $strCurrentTimeZone = (Get-CimInstance -ClassName Win32_TimeZone).StandardName # If running test cases return current date if ($env:NinjaPesterTesting) { return Get-Date } $TZ = [System.TimeZoneInfo]::FindSystemTimeZoneById($strCurrentTimeZone) Return [System.TimeZoneInfo]::ConvertTimeFromUtc($UTCTime, $TZ) } $IssuesFound = $false $Log = [System.Collections.Generic.List[String]]::new() } process { if (-not (Test-IsElevated)) { Write-Error -Message "Access Denied. Please run with Administrator privileges." exit 1 } if (-not $(Test-ConnectedToInternet)) { Write-Host "[Issue] Windows doesn't think it is connected to Internet." $IssuesFound = $true } # Check CryptSvc amd bits services $Service = Get-Service -Name CryptSvc if ($Service.StartType -notlike 'Automatic') { Write-Host "[Issue] (CryptSvc) CryptSvc service is set to $($Service.StartType) but needs to be set to Automatic" $Log.Add("[Issue] (CryptSvc) CryptSvc service is set to $($Service.StartType) but needs to be set to Automatic") $IssuesFound = $true } else { Write-Host "[Info] (CryptSvc) CryptSvc service is set to $($Service.StartType)" $Log.Add("[Info] (CryptSvc) CryptSvc service is set to $($Service.StartType)") } $Service = Get-Service -Name bits if ($Service.StartType -eq 'Disabled') { Write-Host "[Issue] (bits) BITS service is set to $($Service.StartType) but needs to be set to Manual" $Log.Add("[Issue] (bits) BITS service is set to $($Service.StartType) but needs to be set to Manual") $IssuesFound = $true } else { Write-Host "[Info] (bits) BITS service is set to $($Service.StartType)" $Log.Add("[Info] (bits) BITS service is set to $($Service.StartType)") } # Check that Windows Update service is running and isn't disabled $wuService = Get-Service -Name wuauserv -ErrorAction SilentlyContinue if ($wuService.Status -ne "Running") { $Service = Get-Service -Name wuauserv if ($Service.StartType -eq 'Disabled') { Write-Host "[Issue] (wuauserv) Windows Update service is set to $($Service.StartType) but needs to be set to Automatic (Trigger Start) or Manual" $Log.Add("[Issue] (wuauserv) Windows Update service is set to $($Service.StartType) but needs to be set to Automatic (Trigger Start) or Manual") $IssuesFound = $true } else { Write-Host "[Info] (wuauserv) Windows Update service is set to $($Service.StartType)" $Log.Add("[Info] (wuauserv) Windows Update service is set to $($Service.StartType)") } } # Check WaaSMedic $SupportWaaSMedic = [System.Environment]::OSVersion.Version.Build -gt 17600 if ($SupportWaaSMedic) { $Plugins = Test-WaaSMedic $PluginIssues = $Plugins | Where-Object { $_.Parameter["error"] } | ForEach-Object { $PluginErrors = $_.Parameter["error"] "[Potential Issue] WaaSMedic plugin errors found with: $($PluginErrors)" } if ($PluginIssues.Count -gt 1) { Write-Host "[Issue] Found more than 1 plugin errors." $Log.Add("[Issue] Found more than 1 plugin errors.") $PluginIssues | Write-Host $IssuesFound = $true } } # Check if NTP is setup if ("NoSync" -eq (Get-TimeSyncType)) { Write-Host "[Issue] NTP not setup!" $Log.Add("[Issue] NTP not setup!") $IssuesFound = $true } # Check Windows Update logs $EventErrors = Get-HasWinUpdateErrorInLastWeek -AllLastWeekError if ($EventErrors.Count -gt 0) { if (![string]::IsNullOrEmpty($allError.Values)) { Write-Host "[Issue] Event Log has Windows Update errors." $Log.Add("[Issue] Event Log has Windows Update errors.") $errorCodes = $allError.Values -join ';' Write-Host "[Issue] Error codes found: $errorCodes" $Log.Add("[Issue] Error codes found: $errorCodes") $IssuesFound = $true } } # If no issues found, get number of days since the last check for updates happened if (-not $IssuesFound) { $LastCheck = Get-LocalTime $(New-Object -ComObject Microsoft.Update.AutoUpdate).Results.LastSearchSuccessDate Write-Host "[Info] Last checked for updates on $($LastCheck.ToShortDateString())" $Log.Add("[Info] Last checked for updates on $($LastCheck.ToShortDateString())") $LastCheckTimeSpan = New-TimeSpan -Start $LastCheck -End $(Get-Date) if ($LastCheckTimeSpan.TotalDays -gt $Days) { $Days = [System.Math]::Round($LastCheckTimeSpan.TotalDays, 0) Write-Host "[Issue] Windows Update has not checked for updates in over $Days days." $Log.Add("[Issue] Windows Update has not checked for updates in over $Days days.") $IssuesFound = $true } } if ($ResultsCustomField) { Ninja-Property-Set -Name $ResultsCustomField -Value $($Log | Out-String) } if ($IssuesFound) { exit 1 } exit 0 } end { }
Accede a más de 300 scripts en el Dojo de NinjaOne
Análisis detallado
El script funciona realizando una serie de comprobaciones en componentes y servicios clave de Windows Update, cada uno de ellos esencial para que el proceso de actualización funcione correctamente. Aquí tienes un desglose paso a paso de cómo funciona el script para diagnosticar problemas de Windows Update:
1. Control de la elevación de los permisos: el script comienza verificando que se está ejecutando con privilegios de administrador, necesarios para modificar los servicios del sistema y acceder a registros específicos.
2. Prueba de conectividad a Internet: comprueba si el sistema está conectado a Internet, un requisito básico para descargar actualizaciones.
3. Comprobaciones del estado del servicio:
- Servicios criptográficos (CryptSvc): garantiza que los servicios criptográficos estén configurados en «Automático», una configuración necesaria para gestionar los archivos de actualización de forma segura.
- Servicio de transferencia inteligente en segundo plano (BITS): comprueba que BITS no está desactivado, ya que se encarga de transferir archivos en segundo plano, incluidas las actualizaciones.
- Servicio de actualización de Windows (wuauserv): confirma que el servicio Windows Update se está ejecutando y está configurado en el tipo de inicio correcto.
4. Comprobación de WaaSMedic: en los sistemas con una versión de compilación superior a 17600, el script comprueba los plugins WaaSMedic, que se encargan de solucionar automáticamente los problemas relacionados con las actualizaciones.
5. Configuración NTP: el script comprueba si el Protocolo de Tiempo de Red (NTP) está configurado correctamente, asegurando que el reloj del sistema está sincronizado con una fuente de tiempo externa, un factor crucial para el proceso de actualización.
6. Análisis del registro de eventos: revisa los registros de eventos de Windows Update en busca de cualquier error registrado en la última semana, identificando códigos de error específicos que puedan indicar problemas subyacentes.
7. Comprobación de la última actualización: por último, el script determina cuándo fue la última vez que el sistema buscó actualizaciones. Si supera el umbral definido por el usuario (por defecto, 30 días), señala el problema.
Cada una de estas comprobaciones se registra y, si se encuentra algún problema, se informa de él en un resumen que puede guardarse en un campo personalizado para su posterior análisis.
Posibles casos de uso
Imagínate a un profesional de TI que gestiona una flota de estaciones de trabajo para una gran empresa. Un día, varios usuarios informan de que sus sistemas llevan varias semanas sin recibir actualizaciones. En lugar de comprobar manualmente cada sistema, el profesional de TI despliega este script para diagnosticar problemas de Windows Update en todas las estaciones de trabajo.
El script identifica que el servicio Windows Update en varias máquinas está mal configurado, con BITS deshabilitado en otras. También observa que algunos sistemas llevan más de 60 días sin buscar actualizaciones.
Con esta información, el profesional de TI puede rectificar rápidamente los problemas, asegurándose de que todos los sistemas se ponen al día, minimizando los riesgos de seguridad y manteniendo el cumplimiento de las políticas corporativas.
Comparaciones
Este script de PowerShell ofrece un enfoque más automatizado y completo que los métodos tradicionales, como la comprobación manual del estado de los servicios o la búsqueda en los registros de eventos.
Aunque las herramientas basadas en GUI, como el Solucionador de problemas de Windows Update, pueden resolver algunos problemas, a menudo se quedan cortas a la hora de proporcionar información detallada o gestionar varios equipos simultáneamente.
Este script, por otra parte, no sólo identifica los problemas, sino que también ofrece perspectivas claras y procesables, lo que lo convierte en una opción superior para entornos de TI a gran escala.
FAQ
1. ¿Este script puede solucionar los problemas que encuentra?
- No, este script está diseñado para diagnosticar problemas de Windows Update. Sin embargo, proporciona información suficiente para que los profesionales de TI tomen las medidas necesarias para resolver los problemas manualmente.
2. ¿Es este script compatible con todas las versiones de Windows?
- El script es compatible con Windows 10 y Windows Server 2016 o versiones posteriores, lo que garantiza una amplia aplicabilidad en entornos Windows modernos.
3. ¿Qué debo hacer si el script informa de un error con los plugins WaaSMedic?
- Los problemas de WaaSMedic suelen requerir intervención manual. Es posible que tengas que restablecer el servicio WaaSMedic o utilizar herramientas adicionales para hacer frente a los errores específicos del plugin.
Implicaciones
Los resultados de este script pueden tener importantes implicaciones para la seguridad informática. Identificar y solucionar problemas de Windows Update rápidamente puede evitar que se exploten vulnerabilidades no parcheadas, reduciendo el riesgo de ciberataques.
Además, garantizar que las actualizaciones se apliquen de forma coherente ayuda a mantener la estabilidad del sistema, evitando paradas inesperadas que podrían interrumpir las operaciones de la empresa.
Recomendaciones
Cuando se utiliza este script para diagnosticar problemas de Windows Update, se recomienda:
- Ejecutarlo con regularidad: incorpóralo a tu programa de mantenimiento rutinario para asegurarte de que los problemas relacionados con las actualizaciones se detectan a tiempo.
- Analizar detenidamente los registros: presta atención a los detalles de los registros generados por el script para diagnosticar problemas de Windows Update, ya que pueden proporcionar información crítica sobre problemas recurrentes.
- Integrarlo con herramientas de automatización: en entornos a gran escala, considera integrar este script con plataformas de automatización como NinjaOne para agilizar el proceso de diagnóstico en múltiples sistemas.
Reflexiones finales
NinjaOne ofrece una potente plataforma que complementa la funcionalidad de este script. Al integrar el script en los flujos de trabajo automatizados de NinjaOne, los profesionales de TI pueden mejorar su capacidad para diagnosticar problemas de Windows Update en numerosos equipos y de forma simultánea.
Esta integración no sólo ahorra tiempo, sino que garantiza que todos los sistemas permanezcan seguros y actualizados, contribuyendo en última instancia a una infraestructura informática más estable y resistente.