Guía para diagnosticar problemas de Windows Update mediante PowerShell

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

Obtén acceso

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.

Próximos pasos

La creación de un equipo de TI próspero y eficaz requiere contar con una solución centralizada que se convierta en tu principal herramienta de prestación de servicios. NinjaOne permite a los equipos de TI supervisar, gestionar, proteger y dar soporte a todos sus dispositivos, estén donde estén, sin necesidad de complejas infraestructuras locales.

Obtén más información sobre NinjaOne Endpoint Management, echa un vistazo a un tour en vivo o comienza tu prueba gratuita de la plataforma NinjaOne.

Categorías:

Quizá también te interese…

×

¡Vean a NinjaOne en acción!

Al enviar este formulario, acepto la política de privacidad de NinjaOne.

Términos y condiciones de NinjaOne

Al hacer clic en el botón «Acepto» que aparece a continuación, estás aceptando los siguientes términos legales, así como nuestras Condiciones de uso:

  • Derechos de propiedad: NinjaOne posee y seguirá poseyendo todos los derechos, títulos e intereses sobre el script (incluidos los derechos de autor). NinjaOne concede al usuario una licencia limitada para utilizar el script de acuerdo con estos términos legales.
  • Limitación de uso: solo podrás utilizar el script para tus legítimos fines personales o comerciales internos, y no podrás compartirlo con terceros.
  • Prohibición de republicación: bajo ninguna circunstancia está permitido volver a publicar el script en ninguna biblioteca de scripts que pertenezca o esté bajo el control de cualquier otro proveedor de software.
  • Exclusión de garantía: el script se proporciona «tal cual» y «según disponibilidad», sin garantía de ningún tipo. NinjaOne no promete ni garantiza que el script esté libre de defectos o que satisfaga las necesidades o expectativas específicas del usuario.
  • Asunción de riesgos: el uso que el usuario haga del script corre por su cuenta y riesgo. El usuario reconoce que existen ciertos riesgos inherentes al uso del script, y entiende y asume cada uno de esos riesgos.
  • Renuncia y exención: el usuario no hará responsable a NinjaOne de cualquier consecuencia adversa o no deseada que resulte del uso del script y renuncia a cualquier derecho o recurso legal o equitativo que pueda tener contra NinjaOne en relación con su uso del script.
  • CLUF: si el usuario es cliente de NinjaOne, su uso del script está sujeto al Contrato de Licencia para el Usuario Final (CLUF).