Pulizia del disco e automazione della manutenzione

Panoramica

In questo articolo le attività periodiche di pulizia disco in Windows Server vengono suddivise in 4 categorie, illustrando come automatizzarle con script PowerShell indipendenti per ogni obiettivo:

  • Ottimizzazione di WinSxS (riduzione dell’archivio componenti)
  • Pulizia delle cartelle Temp (in base ai giorni di conservazione)
  • Rotazione dei file di log (più percorsi, con giorni di conservazione separati)
  • Backup + svuotamento dei log eventi

Convenzioni delle variabili

Variabile Esempio Note
<<ADMIN_USER>> Administrator Account che esegue le attività pianificate
<<LOG_PATH>> C:\Maintenance\Logs Directory comune per i log degli script
<<BACKUP_PATH>> C:\Maintenance\Backups Directory per il backup dei log eventi
<<DAILY_TEMP_TASK_NAME>> DailyTempCleanup Nome attività di pulizia Temp
<<DAILY_LOG_TASK_NAME>> DailyLogRotation Nome attività di rotazione log
<<MONTHLY_WINSXS_TASK_NAME>> MonthlyWinSxSCleanup Nome attività di ottimizzazione WinSxS
<<MONTHLY_EVENTLOG_TASK_NAME>> MonthlyEventLogMaintenance Nome attività di manutenzione log eventi

Step 1: Panoramica dell’ottimizzazione della cartella WinSxS

C:\Windows\WinSxS è l’archivio componenti (Component Store), che conserva la cronologia degli aggiornamenti e dei componenti di funzionalità. Con il tempo può crescere in modo significativo; è possibile ottimizzarlo con i seguenti comandi DISM:

# Analisi dell’archivio componenti
Dism /Online /Cleanup-Image /AnalyzeComponentStore

# Pulizia dei componenti obsoleti
Dism /Online /Cleanup-Image /StartComponentCleanup

# Rimozione completa delle versioni precedenti (nessun rollback possibile)
Dism /Online /Cleanup-Image /StartComponentCleanup /ResetBase

Poiché /ResetBase rende impossibile disinstallare gli aggiornamenti precedenti, è consigliabile eseguirlo solo in modo pianificato, ad esempio una volta al mese durante una finestra di manutenzione.


Step 2: Panoramica della pulizia delle cartelle Temp

Nelle directory $env:TEMP e C:\Windows\Temp si accumulano file temporanei creati da installer e applicazioni. Invece di cancellare tutto alla cieca, una strategia più sicura è:

“Eliminare i file più vecchi di N giorni”.


Step 3: Panoramica della rotazione dei file di log

I percorsi di log aumentano con il numero di applicazioni e, se conservati a lungo, possono saturare il disco. Poiché è utile assegnare giorni di conservazione diversi per ciascuna directory, è opportuno usare un file di configurazione PSD1 per gestire la rotazione in modo flessibile.


Step 4: Panoramica della manutenzione dei log eventi

I log eventi sono fondamentali per diagnosi guasti e audit, quindi non dovrebbero essere svuotati di frequente senza backup. Una prassi comune è eseguire un backup in formato .evtx e successivamente svuotare il log, ad esempio circa una volta al mese:

# Esempio base di backup e svuotamento
wevtutil epl System C:\Logs\System_20250101.evtx
wevtutil cl System

Step 5: Linee guida di progettazione degli script

In questo articolo prepariamo quattro script distinti, uno per ciascun obiettivo:

  1. cleanup_temp.ps1
    Elimina i file obsoleti nelle cartelle Temp (giorni di conservazione configurabili via parametro).

  2. rotate_logs.ps1
    Usa un file PSD1 per definire le directory di log e i relativi giorni di conservazione, ed elimina i .log scaduti.

  3. optimize_winsxs.ps1
    Esegue /StartComponentCleanup /ResetBase su WinSxS e registra la quantità di spazio liberato.

  4. maintain_eventlogs.ps1
    Esegue il backup dei log eventi specificati e li svuota.

Con questa struttura, dall’Utilità di pianificazione è possibile eseguire selettivamente ogni script (giornaliero/mensile) e adattare facilmente la policy in base alle esigenze.

Esempio di struttura di directory prevista:

C:\
└─ Maintenance\
   ├─ cleanup_temp.ps1               # Pulizia cartelle Temp (giornaliera)
   ├─ rotate_logs.ps1                # Rotazione dei log (giornaliera)
   ├─ optimize_winsxs.ps1            # Ottimizzazione WinSxS (mensile)
   ├─ maintain_eventlogs.ps1         # Backup + svuotamento log eventi (mensile)
   │
   ├─ log_rotation.psd1              # Configurazione rotazione log (più percorsi + giorni)
   │
   ├─ Logs\                          # Log di esecuzione degli script
   │    ├─ cleanup_temp_*.log
   │    ├─ rotate_logs_*.log
   │    ├─ optimize_winsxs_*.log
   │    └─ eventlog_maint_*.log
   │
   └─ Backups\                       # Backup EVTX dei log eventi
        ├─ System_YYYYMMDD.evtx
        ├─ Application_YYYYMMDD.evtx
        └─ Security_YYYYMMDD.evtx

Step 6: Script di pulizia Temp

Questo script elimina in modo ricorsivo file e cartelle più vecchi di DaysToKeep giorni sotto i percorsi specificati in $TempPaths, registrando il risultato in un file di log.
È possibile sovrascrivere DaysToKeep e LogPath tramite parametri.

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 di rotazione dei log

File di configurazione della rotazione (PSD1)

Questo file PSD1 definisce più coppie directory di log + giorni di conservazione. Per aggiungere una nuova directory di log, è sufficiente aggiungere una voce all’array.

log_rotation.psd1

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

Script di rotazione dei log

Questo script legge RotationTargets dal file PSD1 e, per ogni percorso, elimina i file .log più vecchi della soglia configurata, registrando tutte le operazioni nei 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 di ottimizzazione WinSxS

Questo script esegue /StartComponentCleanup /ResetBase su WinSxS e misura la dimensione della directory prima e dopo, registrandola nei log. In questo modo è possibile quantificare lo spazio effettivamente liberato.

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 di manutenzione dei log eventi

Questo script, per i log specificati (per impostazione predefinita System / Application / Security), esegue un backup in formato .evtx nella directory di backup e poi svuota i log originali. Tutti i risultati vengono riportati in un file di log.

maintain_eventlogs.ps1

param(
    [string]$LogPath = "<<LOG_PATH>>",                  # Destinazione per i log (.log)
    [string]$BackupPath = "<<BACKUP_PATH>>",            # Destinazione per i backup EVTX
    [string[]]$EventLogs = @("System", "Application", "Security")
)

# Creazione della directory per i log
if (-not (Test-Path $LogPath)) {
    New-Item $LogPath -ItemType Directory -Force | Out-Null
}

# Creazione della directory per i backup
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) {

    # Genera un nome EVTX univoco per ogni 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: Esempi di registrazione delle attività nell’Utilità di pianificazione

Di seguito vengono mostrati esempi di registrazione di tutte le attività di manutenzione tramite schtasks.exe.
Per una spiegazione dettagliata delle opzioni /SC, /D, /ST, /RU e di altri pattern, vedere:

Gestione dell’Utilità di pianificazione con schtasks.exe


Pulizia Temp (attività giornaliera)

Esegue cleanup_temp.ps1 ogni giorno alle 2:00, eliminando i file Temp più vecchi di 7 giorni.
L’attività viene eseguita con l’account <<ADMIN_USER>> con massimi privilegi (/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

Rotazione log (attività giornaliera)

Esegue rotate_logs.ps1 ogni giorno alle 2:30, ruotando i file .log in più directory in base alla configurazione PSD1.
I giorni di conservazione vengono impostati solo in 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

Ottimizzazione WinSxS (attività mensile)

Esegue optimize_winsxs.ps1 il giorno 1 di ogni mese alle 3:00, lanciando StartComponentCleanup /ResetBase e misurando la dimensione prima/dopo.
Poiché comprende operazioni non reversibili, va pianificata in una finestra di manutenzione.

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

Manutenzione log eventi (attività mensile)

Esegue maintain_eventlogs.ps1 il giorno 1 di ogni mese alle 3:30, spostando i log eventi (System / Application / Security, ecc.) nella directory di backup e svuotandoli.
Adeguare -EventLogs e -BackupPath secondo i requisiti di audit.

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

Riepilogo

In questo articolo la manutenzione del disco in Windows Server è stata scomposta in script specializzati per finalità, e tramite un file di configurazione PSD1 è stato reso possibile gestire in modo flessibile percorsi di log e giorni di conservazione:

  • Pulizia Temp: esecuzione giornaliera, giorni di conservazione configurabili
  • Rotazione log: gestione di più percorsi e policy tramite PSD1
  • Ottimizzazione WinSxS: esecuzione mensile, con misura dello spazio liberato
  • Manutenzione log eventi: esecuzione mensile, con backup EVTX prima dello svuotamento

Questa struttura costituisce una base riutilizzabile per automatizzare la manutenzione del disco nei server Windows in ambienti di produzione, estendibile con ulteriori script e attività programmate.