Limpeza de disco e automação de manutenção

Visão geral

Este artigo mostra como dividir as tarefas periódicas de limpeza de disco em servidores Windows em quatro áreas e automatizá-las com scripts PowerShell independentes para cada objetivo:

  • Otimização do WinSxS (redução do component store)
  • Limpeza de pastas temporárias (por dias de retenção)
  • Rotação de arquivos de log (múltiplos caminhos, cada um com sua retenção)
  • Backup e limpeza de logs de eventos

Convenções de variáveis

Nome da variável Exemplo Observação
<<ADMIN_USER>> Administrator Usuário que executa as tarefas agendadas
<<LOG_PATH>> C:\Maintenance\Logs Diretório comum para logs de execução
<<BACKUP_PATH>> C:\Maintenance\Backups Diretório para backup de logs de eventos
<<DAILY_TEMP_TASK_NAME>> DailyTempCleanup Nome da tarefa de limpeza de Temp
<<DAILY_LOG_TASK_NAME>> DailyLogRotation Nome da tarefa de rotação de logs
<<MONTHLY_WINSXS_TASK_NAME>> MonthlyWinSxSCleanup Nome da tarefa de otimização do WinSxS
<<MONTHLY_EVENTLOG_TASK_NAME>> MonthlyEventLogMaintenance Nome da tarefa de manutenção dos logs de eventos

Step 1: Visão geral da otimização da pasta WinSxS

C:\Windows\WinSxS é o component store que mantém o histórico de atualizações e componentes. Ele tende a crescer ao longo do tempo e pode ser otimizado com os comandos DISM a seguir:

# Analisar o component store
Dism /Online /Cleanup-Image /AnalyzeComponentStore

# Limpar componentes antigos
Dism /Online /Cleanup-Image /StartComponentCleanup

# Remover completamente versões antigas (sem possibilidade de rollback)
Dism /Online /Cleanup-Image /StartComponentCleanup /ResetBase

Como /ResetBase torna impossível desinstalar atualizações antigas, é mais seguro executá-lo apenas em momentos planejados, por exemplo, uma vez por mês durante uma janela de manutenção.


Step 2: Visão geral da limpeza de pastas temporárias

Em $env:TEMP e C:\Windows\Temp acumulam-se arquivos temporários criados por instaladores e aplicações. Em vez de apagar tudo indiscriminadamente, é mais seguro aplicar a política:

“apagar arquivos mais antigos do que X dias”.


Step 3: Visão geral da rotação de arquivos de log

Diretórios de log crescem com o tempo e podem consumir muito espaço em disco. Como é desejável definir dias de retenção específicos por diretório, usamos um arquivo de configuração PSD1 para gerenciar os alvos de forma flexível.


Step 4: Visão geral da manutenção de logs de eventos

Logs de eventos são críticos para análise de incidentes e auditoria, portanto não devem ser limpos com frequência sem backup. Uma prática comum é executar, cerca de uma vez por mês, o seguinte padrão: exportar para .evtx e depois limpar.

# Exemplo básico de backup e limpeza
wevtutil epl System C:\Logs\System_20250101.evtx
wevtutil cl System

Step 5: Diretrizes de desenho dos scripts

Neste artigo, usamos quatro scripts, um para cada objetivo:

  1. cleanup_temp.ps1
    Remove arquivos antigos em pastas temporárias (dias de retenção configuráveis por parâmetro).

  2. rotate_logs.ps1
    Remove arquivos .log sob diretórios definidos em um arquivo de configuração PSD1.

  3. optimize_winsxs.ps1
    Executa /StartComponentCleanup /ResetBase no WinSxS e registra a quantidade de espaço liberado.

  4. maintain_eventlogs.ps1
    Exporta logs de eventos especificados para .evtx e, em seguida, limpa esses logs.

Com essa estrutura, cada script pode ser executado separadamente pelo Agendador de Tarefas, permitindo combinações de agendamentos diários/mensais e ajustes de política com facilidade.

Estrutura de diretórios sugerida:

C:\
└─ Maintenance\
   ├─ cleanup_temp.ps1               # Limpeza de pastas Temp (diária)
   ├─ rotate_logs.ps1                # Rotação de logs (diária)
   ├─ optimize_winsxs.ps1            # Otimização do WinSxS (mensal)
   ├─ maintain_eventlogs.ps1         # Backup + limpeza de logs de eventos (mensal)
   │
   ├─ log_rotation.psd1              # Configuração de rotação (múltiplos caminhos + dias)
   │
   ├─ Logs\                          # Logs de execução de cada script
   │    ├─ cleanup_temp_*.log
   │    ├─ rotate_logs_*.log
   │    ├─ optimize_winsxs_*.log
   │    └─ eventlog_maint_*.log
   │
   └─ Backups\                       # Backups de logs de eventos (EVTX)
        ├─ System_YYYYMMDD.evtx
        ├─ Application_YYYYMMDD.evtx
        └─ Security_YYYYMMDD.evtx

Step 6: Script de limpeza de Temp

Este script remove arquivos e pastas mais antigos que DaysToKeep dias em cada diretório listado em $TempPaths, registrando todos os resultados em um arquivo de log.
Os parâmetros DaysToKeep e LogPath podem ser sobrescritos na linha de comando.

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 rotação de logs

Arquivo de configuração de rotação (PSD1)

Este arquivo PSD1 define vários diretórios de destino e seus respectivos dias de retenção. Para adicionar um novo diretório de logs, basta incluir uma nova entrada.

log_rotation.psd1

@{
    RotationTargets = @(
        @{
            Path       = "C:\Logs\App1"
            DaysToKeep = 7
        },
        @{
            Path       = "C:\Logs\App2"
            DaysToKeep = 30
        },
        @{
            Path       = "<<LOG_PATH>>"
            DaysToKeep = 7
        }
    )
}

Script de rotação de logs

Este script lê RotationTargets a partir do PSD1 e remove arquivos .log em cada caminho cujo LastWriteTime seja mais antigo que o limite calculado. Todas as ações são registradas em 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 otimização do WinSxS

Este script mede o tamanho do diretório WinSxS antes e depois da limpeza e executa /StartComponentCleanup /ResetBase. Assim, é possível ver numericamente o ganho em espaço em disco.

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 manutenção de logs de eventos

Este script exporta os logs especificados (por padrão: System / Application / Security) para arquivos .evtx em um diretório de backup e, em seguida, limpa os logs originais. O arquivo de log registra todos os sucessos e falhas.

maintain_eventlogs.ps1

param(
    [string]$LogPath = "<<LOG_PATH>>",                  # Diretório de logs (.log)
    [string]$BackupPath = "<<BACKUP_PATH>>",            # Diretório de backup EVTX
    [string[]]$EventLogs = @("System", "Application", "Security")
)

# Criar diretório de logs, se necessário
if (-not (Test-Path $LogPath)) {
    New-Item $LogPath -ItemType Directory -Force | Out-Null
}

# Criar diretório de backup, se necessário
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) {

    # Gerar um nome de arquivo 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: Exemplos de registro no Agendador de Tarefas

A seguir, todos os scripts de manutenção são registrados usando schtasks.exe.
Para uma explicação detalhada das opções /SC, /D, /ST, /RU etc., consulte:

Gerenciamento do Agendador de Tarefas com schtasks.exe


Exemplo de tarefa diária de limpeza de Temp

Executa cleanup_temp.ps1 todos os dias às 2:00, removendo arquivos temporários mais antigos que 7 dias.
A tarefa é executada como <<ADMIN_USER>> com privilégios máximos (/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

Exemplo de tarefa diária de rotação de logs

Executa rotate_logs.ps1 todos os dias às 2:30 e rotaciona arquivos .log em vários diretórios com base na configuração PSD1.
Os dias de retenção são controlados apenas pelo arquivo 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

Exemplo de tarefa mensal de otimização do WinSxS

Executa optimize_winsxs.ps1 no dia 1 de cada mês às 3:00, realizando StartComponentCleanup /ResetBase com medição de tamanho.
Como não há rollback, agendar este script em uma janela de manutenção é obrigatório.

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

Exemplo de tarefa mensal de manutenção de logs de eventos

Executa maintain_eventlogs.ps1 no dia 1 de cada mês às 3:30, exportando logs como System / Application / Security para o diretório de backup e depois limpando esses logs.
Ajuste -EventLogs e -BackupPath conforme seus requisitos de auditoria.

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

Resumo

Este artigo apresentou uma abordagem para dividir a manutenção de disco no Windows Server em scripts específicos por objetivo, usando ainda um arquivo de configuração PSD1 para controlar a rotação de logs de forma flexível.

  • Limpeza de Temp: execução diária, com dias de retenção configuráveis
  • Rotação de logs: múltiplos caminhos e retenções definidos em PSD1
  • Otimização do WinSxS: mensal, com registro da redução de tamanho
  • Manutenção de logs de eventos: mensal, com backup em EVTX seguido de limpeza

O resultado é uma automação de manutenção robusta e extensível para ambientes de produção.