Очистка диска и автоматизация обслуживания

Обзор

В этой статье периодические задачи по очистке диска в Windows Server разделяются на четыре категории и автоматизируются с помощью отдельных скриптов PowerShell для каждой задачи:

  • Оптимизация WinSxS (сокращение component store)
  • Очистка временных папок (по числу дней хранения)
  • Ротация файлов журналов (несколько путей, каждый со своим сроком хранения)
  • Резервное копирование и очистка журналов событий

Обозначения переменных

Имя переменной Пример Описание
<<ADMIN_USER>> Administrator Учетная запись, от имени которой выполняются задачи планировщика
<<LOG_PATH>> C:\Maintenance\Logs Общая папка для логов выполнения скриптов
<<BACKUP_PATH>> C:\Maintenance\Backups Папка для резервных копий журналов событий
<<DAILY_TEMP_TASK_NAME>> DailyTempCleanup Имя задачи очистки Temp
<<DAILY_LOG_TASK_NAME>> DailyLogRotation Имя задачи ротации логов
<<MONTHLY_WINSXS_TASK_NAME>> MonthlyWinSxSCleanup Имя задачи оптимизации WinSxS
<<MONTHLY_EVENTLOG_TASK_NAME>> MonthlyEventLogMaintenance Имя задачи обслуживания журналов событий

Step 1: Обзор оптимизации папки WinSxS

C:\Windows\WinSxS — это хранилище компонентов (component store), в котором поддерживается история обновлений и компонент. Со временем оно разрастается и может быть оптимизировано следующими командами DISM:

# Анализ хранилища компонентов
Dism /Online /Cleanup-Image /AnalyzeComponentStore

# Очистка старых компонентов
Dism /Online /Cleanup-Image /StartComponentCleanup

# Полное удаление старых версий (откат невозможен)
Dism /Online /Cleanup-Image /StartComponentCleanup /ResetBase

Так как /ResetBase делает невозможной деинсталляцию старых обновлений, безопаснее выполнять эту операцию только по плану, например один раз в месяц в выделенное окно обслуживания.


Step 2: Обзор очистки временных папок

В $env:TEMP и C:\Windows\Temp накапливаются временные файлы, создаваемые установщиками и приложениями. Вместо того чтобы удалять всё подряд, на практике разумнее использовать политику:

«удалять файлы, которые старше X дней».


Step 3: Обзор ротации файлов журналов

Директории с логами растут вместе с числом приложений и при длительном хранении могут существенно заполнять диск. Поскольку желательно задавать индивидуальный срок хранения для каждой папки, удобнее управлять целями ротации через конфигурационный файл PSD1.


Step 4: Обзор обслуживания журналов событий

Журналы событий критичны для расследования инцидентов и аудита, поэтому их нельзя часто очищать без предварительного резервного копирования. Распространённый шаблон (примерно раз в месяц) — сначала экспортировать в .evtx, затем очистить журнал.

# Базовый пример резервного копирования и очистки
wevtutil epl System C:\Logs\System_20250101.evtx
wevtutil cl System

Step 5: Подход к проектированию скриптов

В статье используются четыре скрипта — по одному на каждую задачу:

  1. cleanup_temp.ps1
    Удаляет старые файлы во временных папках (количество дней хранения задаётся параметром).

  2. rotate_logs.ps1
    Удаляет файлы .log в директориях, заданных в конфигурационном файле PSD1.

  3. optimize_winsxs.ps1
    Выполняет /StartComponentCleanup /ResetBase для WinSxS и регистрирует объём освобождённого места.

  4. maintain_eventlogs.ps1
    Экспортирует указанные журналы событий в .evtx, затем очищает эти журналы.

Такая структура позволяет запускать каждый скрипт отдельно из Планировщика заданий, комбинируя дневные и месячные задания и гибко меняя политику.

Пример структуры каталогов:

C:\
└─ Maintenance\
   ├─ cleanup_temp.ps1               # Очистка временных папок (ежедневно)
   ├─ rotate_logs.ps1                # Ротация логов (ежедневно)
   ├─ optimize_winsxs.ps1            # Оптимизация WinSxS (ежемесячно)
   ├─ maintain_eventlogs.ps1         # Резервное копирование + очистка журналов событий (ежемесячно)
   │
   ├─ log_rotation.psd1              # Конфигурация ротации (несколько путей + дни хранения)
   │
   ├─ Logs\                          # Логи выполнения каждого скрипта
   │    ├─ cleanup_temp_*.log
   │    ├─ rotate_logs_*.log
   │    ├─ optimize_winsxs_*.log
   │    └─ eventlog_maint_*.log
   │
   └─ Backups\                       # Резервные копии журналов событий (EVTX)
        ├─ System_YYYYMMDD.evtx
        ├─ Application_YYYYMMDD.evtx
        └─ Security_YYYYMMDD.evtx

Step 6: Скрипт очистки Temp

Этот скрипт удаляет файлы и каталоги старше DaysToKeep дней в каждой папке из $TempPaths и записывает результат в лог-файл.
Параметры DaysToKeep и LogPath можно переопределить при запуске.

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: Скрипт ротации логов

Конфигурационный файл ротации (PSD1)

Этот файл PSD1 описывает несколько целевых директорий и соответствующее число дней хранения. Для добавления новой папки логов достаточно добавить ещё один элемент в список.

log_rotation.psd1

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

Скрипт ротации логов

Этот скрипт читает RotationTargets из PSD1 и удаляет файлы .log в каждой папке, если их LastWriteTime старше расчётного предела хранения. Все действия пишутся в лог.

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: Скрипт оптимизации WinSxS

Этот скрипт измеряет размер директории WinSxS до и после очистки и выполняет /StartComponentCleanup /ResetBase. Это позволяет количественно увидеть, сколько места было освобождено.

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: Скрипт обслуживания журналов событий

Этот скрипт экспортирует указанные журналы (по умолчанию: System / Application / Security) в файлы .evtx в каталог резервного копирования и затем очищает исходные журналы. Лог-файл детально фиксирует успешные и неуспешные операции.

maintain_eventlogs.ps1

param(
    [string]$LogPath = "<<LOG_PATH>>",                  # Папка для логов (.log)
    [string]$BackupPath = "<<BACKUP_PATH>>",            # Папка для резервных копий EVTX
    [string[]]$EventLogs = @("System", "Application", "Security")
)

# Создать директорию логов при необходимости
if (-not (Test-Path $LogPath)) {
    New-Item $LogPath -ItemType Directory -Force | Out-Null
}

# Создать директорию резервного копирования при необходимости
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) {

    # Уникальное имя EVTX для каждого журнала
    $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: Примеры регистрации задач в Планировщике

Ниже все скрипты обслуживания регистрируются с помощью schtasks.exe.
Подробное объяснение параметров /SC, /D, /ST, /RU и других см. в статье:

Управление Планировщиком заданий с помощью schtasks.exe


Пример ежедневной задачи очистки Temp

Задача cleanup_temp.ps1 выполняется каждый день в 2:00 и удаляет временные файлы старше 7 дней.
Задача запускается от имени <<ADMIN_USER>> с максимальными привилегиями (/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

Пример ежедневной задачи ротации логов

Задача rotate_logs.ps1 выполняется каждый день в 2:30 и ротирует файлы .log в нескольких директориях согласно конфигурации PSD1.
Сроки хранения управляются только через 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

Пример ежемесячной задачи оптимизации WinSxS

Задача optimize_winsxs.ps1 выполняется 1-го числа каждого месяца в 3:00, выполняя StartComponentCleanup /ResetBase с измерением размера до и после.
Так как операция необратима, её необходимо планировать в окно обслуживания.

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

Пример ежемесячной задачи обслуживания журналов событий

Задача maintain_eventlogs.ps1 выполняется 1-го числа каждого месяца в 3:30, экспортируя журналы System / Application / Security в каталог резервного копирования и затем очищая их.
Отрегулируйте параметры -EventLogs и -BackupPath в соответствии с требованиями аудита.

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

Итоги

В статье показано, как разделить обслуживание диска в Windows Server на отдельные скрипты по задачам и использовать конфигурационный файл PSD1 для гибкого управления целевыми директориями ротации логов и сроками их хранения.

  • Очистка Temp: ежедневно, с настраиваемым числом дней хранения
  • Ротация логов: несколько путей и сроки хранения, заданные в PSD1
  • Оптимизация WinSxS: ежемесячно, с логированием объёма освобождённого места
  • Обслуживание журналов событий: ежемесячно, с резервным копированием в EVTX и последующей очисткой

Такой подход даёт надёжную и расширяемую схему автоматизации обслуживания для производственных сред.