Propósito
-
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.
-
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.
-
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.
-
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.
-
Define un
UpdateScopepara 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).
-
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.
-
Muestra mensajes en consola indicando la generación de los archivos.
-
Termina con “Proceso finalizado”.
-
Detalle: qué KBs tiene cada equipo y su estado.
-
Resumen: estadísticas globales de cumplimiento por PCs y servidores.
-
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.
-
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.
-
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.
-
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).
-
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.
-
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.
-
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.
|
# ============================== # CONFIGURACIÓN
# ============================== $DetalleHTML = "C:\tmp\WSUS_Detalle.html" $ResumenHTML = "C:\tmp\WSUS_Resumen.html"
# ==============================
Write-Host "Conectando a WSUS..." -ForegroundColor Cyan
[void][reflection.assembly]::
$wsus = [Microsoft.UpdateServices.
# ============================== # 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('
# ============================== # 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.
$updateScope.
# 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(' "@
$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)</
$compUpdates = $computer. $hasPatch = $false $foundKBs = @()
foreach ($entry in $compUpdates) {
$upd = $wsus.GetUpdate($entry. 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(($
$percentServers = if ($totalServers -gt 0) {[math]::Round(($
$percentTotal = if ($totalTargets -gt 0) {[math]::Round(($
$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('
<table>
<tr><th>Tipo</th><th>Total</
<tr><td>PCs</td><td>$totalPCs<
<tr><td>Servidores</td><td>$
<tr><td>Total</td><td>$ </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:
