domingo, 11 de enero de 2026

WINDOWS. WSUS. PS. Patch Tuesday

 

Propósito

El objetivo del script es auditar el cumplimiento de los equipos respecto al último Patch Tuesday (los parches acumulativos que Microsoft publica el segundo martes de cada mes) y generar dos informes en HTML: uno detallado y otro resumido.
Esta búsqueda, depende como se configure y el número de hosts a tratar podría durar horas o días, de la manera que lo detallo acota mucho el tiempo en mostrar los resultados
Pasos

1. Configuración inicial
  • Define dos rutas de salida:
    • $DetalleHTML: informe detallado por equipo.
    • $ResumenHTML: informe resumen con estadísticas.
  • Carga la librería de administración de WSUS y se conecta al servidor WSUS.
2. Cálculo del último Patch Tuesday
  • Función Get-SecondTuesday: calcula el segundo martes de un mes dado.
  • El script determina si ya ha pasado el Patch Tuesday de este mes; si no, toma el del mes anterior.
  • Resultado: $lastPatchTuesday → fecha del último Patch Tuesday aplicable.
3. Selección de actualizaciones relevantes
  • Obtiene todas las actualizaciones de WSUS.
  • Filtra solo las que:
    • Fueron publicadas exactamente en la fecha del Patch Tuesday.
    • No están rechazadas (IsDeclined -eq $false).
    • Contienen en el título “Cumulative Update”.
  • Extrae la lista única de KBs ($PatchKbs) correspondientes a esos parches.
4. Clasificación de equipos
  • Obtiene todos los equipos registrados en WSUS.
  • Divide en dos grupos:
    • PCs: cuyo nombre de dominio empieza por W (tal como tengo las estaciones de trabajo).
    • Servidores: el resto.
  • Calcula totales y prepara una lista combinada con el tipo de cada equipo.
5. Validación de instalación por equipo
  • Define un UpdateScope para considerar solo actualizaciones instaladas.
  • Para cada equipo:
    • Recupera las actualizaciones instaladas.
    • Comprueba si alguna coincide con los KBs del Patch Tuesday.
    • Marca el equipo como:
      • COMPLIANT (cumple) si tiene el parche.
      • NO COMPLIANT si no lo tiene.
    • Registra los KBs encontrados.
  • Genera un informe detallado en HTML con:
    • Nombre del equipo.
    • KBs encontrados.
    • Estado (verde = compliant, rojo = no compliant).
6. Generación del resumen
  • Calcula:
    • Número de PCs y servidores compliant vs no compliant.
    • Porcentajes de cumplimiento por tipo y global.
  • Construye un informe resumen en HTML con una tabla:
    • Filas: PCs, Servidores, Total.
    • Columnas: Total, Compliant, No Compliant, % Cumplimiento.
  • Guarda el archivo en $ResumenHTML.
7. Finalización
  • Muestra mensajes en consola indicando la generación de los archivos.
  • Termina con “Proceso finalizado”.
 En resumen
Este script es un auditor automático de cumplimiento de parches acumulativos del último Patch Tuesday en WSUS.
  • Detalle: qué KBs tiene cada equipo y su estado.
  • Resumen: estadísticas globales de cumplimiento por PCs y servidores.
Es muy útil para:
  • Administradores de sistemas que necesitan verificar rápidamente si todos los equipos han recibido el último parche crítico.
  • Informes ejecutivos: el resumen HTML da una visión clara del nivel de cumplimiento en la organización.
Por entenderlo mejor, Microsoft publica cada mes un Cumulative Update (LCU) específico para cada versión de Windows soportada. Estos parches son acumulativos, es decir, incluyen todas las correcciones de seguridad y calidad anteriores. Por tanto, no es necesario instalar parches antiguos: basta con aplicar el último acumulativo correspondiente al sistema operativo y versión del host. No se “deja” ninguno atrás, porque cada LCU ya contiene todo lo previo
Detalle técnico

1. Cumulative Update mensual (Patch Tuesday)
  • El segundo martes de cada mes Microsoft publica un Monthly Security Update Release.
  • Este paquete es un Latest Cumulative Update (LCU) que:
    • Incluye todas las correcciones de seguridad y calidad anteriores.
    • Añade las nuevas correcciones de ese mes.
  • Se distribuye por Windows Update, WSUS, Microsoft Update Catalog y herramientas de gestión como Intune o ConfigMgr.
2. Qué significa “cumulativo”
  • Si un equipo instala el LCU de noviembre, automáticamente contiene:
    • Todas las correcciones críticas y de seguridad de octubre, septiembre, etc.
    • No hace falta instalar manualmente los LCUs anteriores.
  • Esto evita la fragmentación y asegura que el sistema queda protegido con un único paquete.
3. Otros tipos de lanzamientos
  • Preview releases (4º martes del mes): opcionales, no de seguridad, sirven para validar cambios antes del Patch Tuesday siguiente.
  • Out-of-band (OOB) releases: excepcionales, cuando hay una vulnerabilidad crítica que no puede esperar. También son acumulativos y sustituyen al último LCU.
  • Feature updates: una vez al año, cambian la versión de Windows (ej. 22H2 → 23H2).
4. Respuesta a tu duda
  • Sí, cada mes hay un acumulativo por versión de Windows soportada.
  • No es necesario mirar hacia atrás en parches críticos o de seguridad: el último LCU ya los incluye.
  • Excepción: si un host está en una versión de Windows que ya no recibe soporte, entonces no tendrá LCUs nuevos y quedará sin parches críticos.
Conclusión práctica para WSUS y compliance:  Cuando audites, basta con comprobar que cada equipo tiene instalado el último LCU aplicable a su versión de Windows. No necesitas validar parches anteriores, porque ya están integrados en ese acumulativo.
Cómo funcionan los acumulativos de Windows
  • Acumulativos mensuales (LCU – Latest Cumulative Update):  Cada mes Microsoft publica un LCU que incluye todas las correcciones anteriores de seguridad y calidad para esa versión concreta de Windows. → Si instalas el último, ya tienes todo lo anterior.
  • ¿Crece indefinidamente el tamaño?
    • En teoría, sí: porque cada nuevo LCU contiene todas las correcciones desde el lanzamiento de la versión del sistema operativo.
    • En la práctica, Microsoft aplica mecanismos de compresión y supersedencia:
      • Los LCUs reemplazan archivos binarios completos, no “parches diferenciales”.
      • Se optimizan con tecnologías como Component Store Cleanup (WinSxS) y servicing stack updates (SSU).
      • Por eso, aunque el paquete sea acumulativo, el tamaño no crece de forma lineal mes a mes.
      • Normalmente los LCUs rondan entre 200 MB y 600 MB (dependiendo de la versión y arquitectura), y se mantienen en ese rango.
  • Desde cuándo acumulan:
    • Cada LCU acumula desde el release inicial de esa versión (ej. Windows 10 22H2, Windows Server 2019).
    • Cuando aparece una nueva versión (ej. Windows 11 23H2), se “resetea” el ciclo: los LCUs de esa versión acumulan solo desde su propio lanzamiento.
Conclusión práctica
  • Sí, cada mes el acumulativo incluye todo lo anterior.
  • No, el tamaño no crece indefinidamente, porque Microsoft optimiza y mantiene un tamaño relativamente estable.
  • Como administrador: basta con desplegar el último LCU aplicable a cada versión de Windows. No es necesario preocuparse por instalar históricos ni por que el paquete se vuelva inmanejable.
Código

Código en Power shell ejecutado desde en propio servidor WSUS


# ===========================================

# CONFIGURACIÓN

# ===========================================

$DetalleHTML = "C:\tmp\WSUS_Detalle.html"

$ResumenHTML = "C:\tmp\WSUS_Resumen.html"

# ===========================================

 

Write-Host "Conectando a WSUS..." -ForegroundColor Cyan

[void][reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")

$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer()

 

# ===========================================

# 1. CALCULAR EL ÚLTIMO PATCH TUESDAY

# ===========================================

function Get-SecondTuesday {

    param ($year, $month)

    $firstDay = Get-Date -Year $year -Month $month -Day 1

    $tuesdays = 1..14 | ForEach-Object { $firstDay.AddDays($_ - 1) } | Where-Object { $_.DayOfWeek -eq 'Tuesday' }

    return $tuesdays[1]

}

 

$today = Get-Date

$secondTuesdayThisMonth = Get-SecondTuesday -year $today.Year -month $today.Month

if ($today -ge $secondTuesdayThisMonth) {

    $lastPatchTuesday = $secondTuesdayThisMonth

} else {

    $prevMonth = $today.AddMonths(-1)

    $lastPatchTuesday = Get-SecondTuesday -year $prevMonth.Year -month $prevMonth.Month

}

Write-Host "Último Patch Tuesday aplicable: $($lastPatchTuesday.ToString('yyyy-MM-dd'))" -ForegroundColor Green

 

# ===========================================

# 2. BUSCAR SOLO “CUMULATIVE UPDATE” DEL PATCH TUESDAY

# ===========================================

$updates = $wsus.GetUpdates() | Where-Object {

    $_.CreationDate.Date -eq $lastPatchTuesday.Date -and

    $_.IsDeclined -eq $false -and

    $_.Title -match "Cumulative Update"

}

 

if ($updates.Count -eq 0) {

    Write-Host "? No se encontraron Cumulative Updates publicados en esa fecha." -ForegroundColor Red

    exit

}

 

# Lista única de KBs del Patch Tuesday

$PatchKbs = $updates | ForEach-Object { $_.KnowledgebaseArticles } | Select-Object -Unique

 

# ===========================================

# 3. FILTRAR EQUIPOS: PCs (P*) y SERVIDORES

# ===========================================

$pcTargets = $wsus.GetComputerTargets() | Where-Object { $_.FullDomainName -match "^W" }

$serverTargets = $wsus.GetComputerTargets() | Where-Object { $_.FullDomainName -notmatch "^W" }

 

$totalPCs = $pcTargets.Count

$totalServers = $serverTargets.Count

$totalTargets = $totalPCs + $totalServers

 

Write-Host "`nEquipos analizados:"

Write-Host " - PCs (P*): $totalPCs" -ForegroundColor Cyan

Write-Host " - Servidores: $totalServers" -ForegroundColor Cyan

Write-Host "Total: $totalTargets" -ForegroundColor Cyan

 

# Combinar ambos grupos

$allTargets = @()

$allTargets += $pcTargets | ForEach-Object { [PSCustomObject]@{Computer=$_; Tipo="PC"} }

$allTargets += $serverTargets | ForEach-Object { [PSCustomObject]@{Computer=$_; Tipo="Servidor"} }

 

# ===========================================

# 4. VALIDAR INSTALACIÓN POR EQUIPO Y GENERAR DETALLE HTML

# ===========================================

$updateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope

$updateScope.IncludedInstallationStates = @('Installed')

 

# Iniciar HTML detalle

$detalle = @"

<html>

<head>

<title>Detalle WSUS Patch Tuesday</title>

<style>

body { font-family: Arial; font-size: 14px; }

.ok { color: green; font-weight: bold; }

.fail { color: red; font-weight: bold; }

h2 { background-color: #EEE; padding: 6px; }

</style>

</head>

<body>

<h1>Detalle WSUS – Patch Tuesday $($lastPatchTuesday.ToString('yyyy-MM-dd'))</h1>

"@

 

$installedCount = 0

$results = @()

 

foreach ($target in $allTargets) {

    $computer = $target.Computer

    $tipo = $target.Tipo

 

    Write-Host "`n---------------------------------------------"

    Write-Host "${tipo}: $($computer.FullDomainName)" -ForegroundColor Cyan

    Write-Host "---------------------------------------------"

 

    $detalle += "<h2>${tipo}: $($computer.FullDomainName)</h2>"

 

    $compUpdates = $computer.GetUpdateInstallationInfoPerUpdate($updateScope)

    $hasPatch = $false

    $foundKBs = @()

 

    foreach ($entry in $compUpdates) {

        $upd = $wsus.GetUpdate($entry.UpdateId)

        foreach ($kb in $upd.KnowledgebaseArticles) {

            if ($PatchKbs -contains $kb) {

                $hasPatch = $true

                $foundKBs += $kb

            }

        }

    }

 

    if ($hasPatch) {

        $status = "<span class='ok'>COMPLIANT</span>"

        Write-Host "Estado: COMPLIANT" -ForegroundColor Green

        $installedCount++

    } else {

        $status = "<span class='fail'>NO COMPLIANT</span>"

        Write-Host "Estado: NO COMPLIANT" -ForegroundColor Red

    }

 

    if ($foundKBs.Count -gt 0) {

        $kbList = $foundKBs -join ", "

        Write-Host "KBs encontrados: $kbList" -ForegroundColor Green

        $detalle += "<p>KBs encontrados: $kbList</p>"

    } else {

        Write-Host "KBs encontrados: Ninguno" -ForegroundColor Yellow

        $detalle += "<p>KBs encontrados: Ninguno</p>"

    }

 

    $detalle += "<p>Estado: $status</p>"

 

    # Registrar resultados

    $results += [PSCustomObject]@{

        Equipo   = $computer.FullDomainName

        Tipo     = $tipo

        Estado   = if ($hasPatch) {"Compliant"} else {"No Compliant"}

        KBs      = $foundKBs -join ", "

    }

}

 

$detalle += "</body></html>"

$detalle | Out-File $DetalleHTML -Encoding UTF8

Write-Host "`n? Archivo de detalle generado: $DetalleHTML" -ForegroundColor Cyan

 

# ===========================================

# 5. GENERAR RESUMEN HTML FINAL

# ===========================================

$totalPCsCompliant = ($results | Where-Object { $_.Tipo -eq "PC" -and $_.Estado -eq "Compliant" }).Count

$totalServersCompliant = ($results | Where-Object { $_.Tipo -eq "Servidor" -and $_.Estado -eq "Compliant" }).Count

 

$notPCsCompliant = $totalPCs - $totalPCsCompliant

$notServersCompliant = $totalServers - $totalServersCompliant

 

$percentPCs = if ($totalPCs -gt 0) {[math]::Round(($totalPCsCompliant/$totalPCs)*100,2)} else {0}

$percentServers = if ($totalServers -gt 0) {[math]::Round(($totalServersCompliant/$totalServers)*100,2)} else {0}

$percentTotal = if ($totalTargets -gt 0) {[math]::Round(($installedCount/$totalTargets)*100,2)} else {0}

 

$resumen = @"

<html>

<head>

<title>Resumen WSUS Patch Tuesday</title>

<style>

body { font-family: Arial; font-size: 14px; }

.ok { color: green; font-weight: bold; }

.fail { color: red; font-weight: bold; }

table { border-collapse: collapse; width: 70%; }

th, td { border: 1px solid #999; padding: 8px; }

th { background-color: #DDD; }

</style>

</head>

<body>

<h1>Resumen WSUS – Patch Tuesday $($lastPatchTuesday.ToString('yyyy-MM-dd'))</h1>

 

<table>

<tr><th>Tipo</th><th>Total</th><th>Compliant</th><th>No Compliant</th><th>% Cumplimiento</th></tr>

<tr><td>PCs</td><td>$totalPCs</td><td><span class='ok'>$totalPCsCompliant</span></td><td><span class='fail'>$notPCsCompliant</span></td><td>$percentPCs %</td></tr>

<tr><td>Servidores</td><td>$totalServers</td><td><span class='ok'>$totalServersCompliant</span></td><td><span class='fail'>$notServersCompliant</span></td><td>$percentServers %</td></tr>

<tr><td>Total</td><td>$totalTargets</td><td><span class='ok'>$installedCount</span></td><td><span class='fail'>$($totalTargets-$installedCount)</span></td><td>$percentTotal %</td></tr>

</table>

 

</body>

</html>

"@

 

$resumen | Out-File $ResumenHTML -Encoding UTF8

Write-Host "`n? Archivo de resumen generado: $ResumenHTML" -ForegroundColor Cyan

 

Write-Host "`nProceso finalizado." -ForegroundColor Green

  

Link: https://learn.microsoft.com/en-us/windows/deployment/update/release-cycle

 

 

by GoN | Published: Jan 2026 | Last Updated: