- Descripción general
- Convención de variables
- Step 1: Resumen de la optimización de la carpeta WinSxS
- Step 2: Resumen de la limpieza de carpetas temporales
- Step 3: Resumen de la rotación de archivos de registro
- Step 4: Resumen del mantenimiento de registros de eventos
- Step 5: Directrices de diseño de los scripts
- Step 6: Script de limpieza de Temp
- Step 7: Script de rotación de logs
- Step 8: Script de optimización de WinSxS
- Step 9: Script de mantenimiento de registros de eventos
- Step 10: Ejemplos de registro en el Programador de tareas
- Resumen
Descripción general
En este artículo se muestran las tareas periódicas de limpieza de disco en Windows Server divididas en cuatro categorías y se explica cómo automatizarlas mediante scripts independientes de PowerShell para cada propósito:
- Optimización de WinSxS (reducción del almacén de componentes)
- Limpieza de carpetas temporales (por días de retención)
- Rotación de archivos de registro (varias rutas, cada una con su propio período de retención)
- Copia de seguridad y limpieza de registros de eventos
Convención de variables
| Nombre de la variable | Ejemplo | Comentario |
|---|---|---|
<<ADMIN_USER>> |
Administrator |
Usuario con el que se ejecutan las tareas programadas |
<<LOG_PATH>> |
C:\Maintenance\Logs |
Directorio común de registros de ejecución |
<<BACKUP_PATH>> |
C:\Maintenance\Backups |
Directorio de copia de seguridad de registros de eventos |
<<DAILY_TEMP_TASK_NAME>> |
DailyTempCleanup |
Nombre de la tarea de limpieza de Temp |
<<DAILY_LOG_TASK_NAME>> |
DailyLogRotation |
Nombre de la tarea de rotación de logs |
<<MONTHLY_WINSXS_TASK_NAME>> |
MonthlyWinSxSCleanup |
Nombre de la tarea de optimización de WinSxS |
<<MONTHLY_EVENTLOG_TASK_NAME>> |
MonthlyEventLogMaintenance |
Nombre de la tarea de mantenimiento de registros de eventos |
Step 1: Resumen de la optimización de la carpeta WinSxS
C:\Windows\WinSxS es el almacén de componentes (component store) donde se conserva el historial de actualizaciones y componentes. Tiende a crecer con el tiempo y puede optimizarse con los siguientes comandos DISM:
# Análisis del almacén de componentes
Dism /Online /Cleanup-Image /AnalyzeComponentStore
# Limpieza de componentes antiguos
Dism /Online /Cleanup-Image /StartComponentCleanup
# Eliminación completa de versiones antiguas (sin posibilidad de rollback)
Dism /Online /Cleanup-Image /StartComponentCleanup /ResetBase
Como /ResetBase impide desinstalar actualizaciones antiguas, lo más seguro es ejecutarlo únicamente en momentos planificados, por ejemplo una vez al mes durante una ventana de mantenimiento.
Step 2: Resumen de la limpieza de carpetas temporales
En $env:TEMP y C:\Windows\Temp se acumulan archivos temporales creados por instaladores y aplicaciones. En lugar de eliminarlos todos sin distinción, en la práctica es más razonable aplicar la política:
“eliminar archivos más antiguos que X días”.
Step 3: Resumen de la rotación de archivos de registro
Los directorios de logs crecen con el número de aplicaciones y con el tiempo pueden consumir mucho espacio de disco. Como es deseable establecer días de retención específicos por directorio, utilizaremos un archivo de configuración PSD1 para definir múltiples rutas y su retención de forma flexible.
Step 4: Resumen del mantenimiento de registros de eventos
Los registros de eventos son fundamentales para el análisis de incidencias y la auditoría, por lo que no deben limpiarse con frecuencia sin copia de seguridad previa. Un patrón habitual, ejecutado aproximadamente una vez al mes, es exportar primero a .evtx y luego limpiar el registro.
# Forma básica de copia de seguridad y limpieza
wevtutil epl System C:\Logs\System_20250101.evtx
wevtutil cl System
Step 5: Directrices de diseño de los scripts
En este artículo se definen cuatro scripts, uno para cada objetivo:
-
cleanup_temp.ps1
Elimina archivos antiguos en carpetas temporales (días de retención configurables como parámetro). -
rotate_logs.ps1
Elimina archivos.logen los directorios definidos en un archivo de configuración PSD1. -
optimize_winsxs.ps1
Ejecuta/StartComponentCleanup /ResetBaseen WinSxS y registra la cantidad de espacio liberado. -
maintain_eventlogs.ps1
Exporta determinados registros de eventos a.evtxy posteriormente limpia dichos registros.
Con esta estructura, cada script se puede ejecutar de forma independiente desde el Programador de tareas, lo que permite combinar tareas diarias/mensuales y ajustar la política con flexibilidad.
Estructura de directorios de ejemplo:
C:\
└─ Maintenance\
├─ cleanup_temp.ps1 # Limpieza de carpetas Temp (diaria)
├─ rotate_logs.ps1 # Rotación de logs (diaria)
├─ optimize_winsxs.ps1 # Optimización de WinSxS (mensual)
├─ maintain_eventlogs.ps1 # Copia de seguridad + limpieza de registros de eventos (mensual)
│
├─ log_rotation.psd1 # Configuración de rotación (varias rutas + días)
│
├─ Logs\ # Registros de ejecución de cada script
│ ├─ cleanup_temp_*.log
│ ├─ rotate_logs_*.log
│ ├─ optimize_winsxs_*.log
│ └─ eventlog_maint_*.log
│
└─ Backups\ # Copias de seguridad de registros de eventos (EVTX)
├─ System_YYYYMMDD.evtx
├─ Application_YYYYMMDD.evtx
└─ Security_YYYYMMDD.evtx
Step 6: Script de limpieza de Temp
Este script elimina archivos y carpetas más antiguos que DaysToKeep días bajo los directorios definidos en $TempPaths, y registra los resultados en un archivo de log.
Los parámetros DaysToKeep y LogPath pueden sobrescribirse en la llamada.
cleanup_temp.ps1
param(
[string[]]$TempPaths = @("$env:TEMP", "C:\Windows\Temp"),
[int]$DaysToKeep = 7,
[string]$LogPath = "<<LOG_PATH>>"
)
if (-not (Test-Path $LogPath)) {
New-Item $LogPath -ItemType Directory -Force | Out-Null
}
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$logFile = Join-Path $LogPath "cleanup_temp_$timestamp.log"
function Write-Log($Message) {
"[{0}] {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Message |
Tee-Object -FilePath $logFile -Append
}
Write-Log "===== Temp cleanup started ====="
Write-Log "DaysToKeep = $DaysToKeep"
$limitDate = (Get-Date).AddDays(-$DaysToKeep)
foreach ($path in $TempPaths) {
if (-not (Test-Path $path)) {
Write-Log "Skip: Not found -> $path"
continue
}
Write-Log "Processing: $path"
Get-ChildItem -Path $path -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -lt $limitDate } |
ForEach-Object {
try {
Remove-Item -LiteralPath $_.FullName -Force -Recurse -ErrorAction Stop
Write-Log "Deleted: $($_.FullName)"
}
catch {
Write-Log "Failed: $($_.FullName) - $($_.Exception.Message)"
}
}
}
Write-Log "===== Temp cleanup finished ====="
Step 7: Script de rotación de logs
Archivo de configuración de rotación (PSD1)
Este archivo PSD1 define varios directorios de destino y sus correspondientes días de retención. Para añadir un nuevo directorio de logs, basta con incluir una nueva entrada.
log_rotation.psd1
@{
RotationTargets = @(
@{
Path = "C:\Logs\App1"
DaysToKeep = 7
},
@{
Path = "C:\Logs\App2"
DaysToKeep = 30
},
@{
Path = "<<LOG_PATH>>"
DaysToKeep = 7
}
)
}
Script de rotación de logs
Este script lee RotationTargets desde el PSD1 y elimina los archivos .log en cada ruta cuyo LastWriteTime sea anterior al límite calculado. Todas las acciones se registran en un archivo de log.
rotate_logs.ps1
param(
[string]$ConfigPath = "C:\Maintenance\log_rotation.psd1",
[string]$LogPath = "<<LOG_PATH>>"
)
if (-not (Test-Path $LogPath)) {
New-Item $LogPath -ItemType Directory -Force | Out-Null
}
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$logFile = Join-Path $LogPath "rotate_logs_$timestamp.log"
function Write-Log($Message) {
"[{0}] {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Message |
Tee-Object -FilePath $logFile -Append
}
Write-Log "===== Log rotation started ====="
Write-Log "ConfigPath = $ConfigPath"
if (-not (Test-Path $ConfigPath)) {
Write-Log "Config file not found. Exit."
exit 1
}
try {
$config = Import-PowerShellDataFile -Path $ConfigPath
}
catch {
Write-Log "Config load failed: $($_.Exception.Message)"
exit 1
}
foreach ($target in $config.RotationTargets) {
$targetPath = $target.Path
$daysToKeep = [int]$target.DaysToKeep
if (-not (Test-Path $targetPath)) {
Write-Log "Skip (not found): $targetPath"
continue
}
$limitDate = (Get-Date).AddDays(-$daysToKeep)
Write-Log "Path=$targetPath DaysToKeep=$daysToKeep Limit=$limitDate"
Get-ChildItem -Path $targetPath -Recurse -Include *.log -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -lt $limitDate } |
ForEach-Object {
try {
Remove-Item -LiteralPath $_.FullName -Force -ErrorAction Stop
Write-Log "Deleted: $($_.FullName)"
}
catch {
Write-Log "Failed: $($_.FullName) - $($_.Exception.Message)"
}
}
}
Write-Log "===== Log rotation finished ====="
Step 8: Script de optimización de WinSxS
Este script mide el tamaño del directorio WinSxS antes y después de la limpieza y ejecuta /StartComponentCleanup /ResetBase. De esta forma, se puede comprobar numéricamente cuánto espacio se ha recuperado.
optimize_winsxs.ps1
param(
[string]$LogPath = "<<LOG_PATH>>"
)
if (-not (Test-Path $LogPath)) {
New-Item $LogPath -ItemType Directory -Force | Out-Null
}
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$logFile = Join-Path $LogPath "optimize_winsxs_$timestamp.log"
function Write-Log($Message) {
"[{0}] {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Message |
Tee-Object -FilePath $logFile -Append
}
Write-Log "===== WinSxS optimization started ====="
$winsxsPath = "C:\Windows\WinSxS"
Write-Log "Measuring WinSxS size before..."
$sizeBefore = (Get-ChildItem $winsxsPath -Recurse -Force -ErrorAction SilentlyContinue |
Measure-Object Length -Sum).Sum
$sizeBeforeGB = [math]::Round($sizeBefore / 1GB, 2)
Write-Log "Before: $sizeBeforeGB GB"
Dism /Online /Cleanup-Image /AnalyzeComponentStore |
Out-File (Join-Path $LogPath "dism_before_$timestamp.txt")
Write-Log "Running StartComponentCleanup /ResetBase..."
Dism /Online /Cleanup-Image /StartComponentCleanup /ResetBase |
Out-File (Join-Path $LogPath "dism_exec_$timestamp.txt")
Write-Log "Measuring WinSxS size after..."
$sizeAfter = (Get-ChildItem $winsxsPath -Recurse -Force -ErrorAction SilentlyContinue |
Measure-Object Length -Sum).Sum
$sizeAfterGB = [math]::Round($sizeAfter / 1GB, 2)
Write-Log "After: $sizeAfterGB GB"
Write-Log ("Reduced: {0} GB" -f ([math]::Round(($sizeBefore - $sizeAfter)/1GB,2)))
Dism /Online /Cleanup-Image /AnalyzeComponentStore |
Out-File (Join-Path $LogPath "dism_after_$timestamp.txt")
Write-Log "===== WinSxS optimization finished ====="
Step 9: Script de mantenimiento de registros de eventos
Este script exporta los registros especificados (por defecto: System / Application / Security) a archivos .evtx en un directorio de copia de seguridad y luego limpia los registros originales. El archivo de log detalla todos los éxitos y errores.
maintain_eventlogs.ps1
param(
[string]$LogPath = "<<LOG_PATH>>", # Directorio de registros (.log)
[string]$BackupPath = "<<BACKUP_PATH>>", # Directorio de backup EVTX
[string[]]$EventLogs = @("System", "Application", "Security")
)
# Crear el directorio de logs, si es necesario
if (-not (Test-Path $LogPath)) {
New-Item $LogPath -ItemType Directory -Force | Out-Null
}
# Crear el directorio de backup, si es necesario
if (-not (Test-Path $BackupPath)) {
New-Item $BackupPath -ItemType Directory -Force | Out-Null
}
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$logFile = Join-Path $LogPath "eventlog_maint_$timestamp.log"
function Write-Log($Message) {
"[{0}] {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Message |
Tee-Object -FilePath $logFile -Append
}
Write-Log "===== Event log maintenance started ====="
Write-Log "BackupPath = $BackupPath"
foreach ($name in $EventLogs) {
# Generar un nombre de archivo EVTX único por log
$destEvtx = Join-Path $BackupPath ("{0}_{1}.evtx" -f $name, $timestamp)
try {
Write-Log "Export: $name -> $destEvtx"
wevtutil epl $name $destEvtx
Write-Log "Clear: $name"
wevtutil cl $name
}
catch {
Write-Log "Failed: $name - $($_.Exception.Message)"
}
}
Write-Log "===== Event log maintenance finished ====="
Step 10: Ejemplos de registro en el Programador de tareas
A continuación, todos los scripts de mantenimiento se registran usando schtasks.exe.
Para una explicación detallada de las opciones /SC, /D, /ST, /RU, etc., consulte:
Administración del Programador de tareas con schtasks.exe
Ejemplo de tarea diaria de limpieza de Temp
Ejecuta cleanup_temp.ps1 todos los días a las 2:00, eliminando archivos temporales más antiguos que 7 días.
La tarea se ejecuta como <<ADMIN_USER>> con máximos privilegios (/RL HIGHEST).
schtasks /Create `
/TN "<<DAILY_TEMP_TASK_NAME>>" `
/TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Maintenance\cleanup_temp.ps1 -DaysToKeep 7 -LogPath <<LOG_PATH>>" `
/SC DAILY `
/ST 02:00 `
/RU "<<ADMIN_USER>>" `
/RL HIGHEST `
/F
Ejemplo de tarea diaria de rotación de logs
Ejecuta rotate_logs.ps1 todos los días a las 2:30 y rota los archivos .log de varios directorios según la configuración definida en el PSD1.
Los días de retención se controlan exclusivamente a través de log_rotation.psd1.
schtasks /Create `
/TN "<<DAILY_LOG_TASK_NAME>>" `
/TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Maintenance\rotate_logs.ps1 -ConfigPath C:\Maintenance\log_rotation.psd1 -LogPath <<LOG_PATH>>" `
/SC DAILY `
/ST 02:30 `
/RU "<<ADMIN_USER>>" `
/RL HIGHEST `
/F
Ejemplo de tarea mensual de optimización de WinSxS
Ejecuta optimize_winsxs.ps1 el día 1 de cada mes a las 3:00, realizando StartComponentCleanup /ResetBase con medición del tamaño.
Dado que no existe posibilidad de rollback, este script debe ejecutarse dentro de una ventana de mantenimiento planificada.
schtasks /Create `
/TN "<<MONTHLY_WINSXS_TASK_NAME>>" `
/TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Maintenance\optimize_winsxs.ps1 -LogPath <<LOG_PATH>>" `
/SC MONTHLY `
/D 1 `
/ST 03:00 `
/RU "<<ADMIN_USER>>" `
/RL HIGHEST `
/F
Ejemplo de tarea mensual de mantenimiento de registros de eventos
Ejecuta maintain_eventlogs.ps1 el día 1 de cada mes a las 3:30, exportando registros como System / Application / Security al directorio de copia de seguridad y limpiándolos a continuación.
Ajuste -EventLogs y -BackupPath según sus requisitos de auditoría.
schtasks /Create `
/TN "<<MONTHLY_EVENTLOG_TASK_NAME>>" `
/TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Maintenance\maintain_eventlogs.ps1 -LogPath <<LOG_PATH>>" `
/SC MONTHLY `
/D 1 `
/ST 03:30 `
/RU "<<ADMIN_USER>>" `
/RL HIGHEST `
/F
Resumen
En este artículo se ha mostrado cómo dividir el mantenimiento de disco en Windows Server en scripts específicos por objetivo y cómo utilizar un archivo de configuración PSD1 para controlar de forma flexible los destinos de rotación de logs y sus períodos de retención.
- Limpieza de Temp: diaria, con días de retención configurables
- Rotación de logs: varios caminos y retenciones definidos en PSD1
- Optimización de WinSxS: mensual, con registro del espacio liberado
- Mantenimiento de registros de eventos: mensual, con copia de seguridad en EVTX seguida de limpieza
Con ello se obtiene una automatización de mantenimiento robusta y extensible para entornos de producción.
