ディスククリーンアップと保守自動化

概要

本記事では、Windows Server の定期実行ディスククリーンアップ作業を以下の4つに分類し、目的ごとに独立した PowerShell スクリプトを用いて自動化する方法を解説します。

  • WinSxS の最適化(コンポーネントストア削減)
  • Temp フォルダのクリーンアップ(保持日数指定)
  • ログファイルローテーション(複数パス・保持日数指定)
  • イベントログのバックアップ+クリア

変数表記について

変数名 設定例 備考
<<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 はコンポーネントストアであり、更新プログラムや機能コンポーネントの履歴を保持します。長期間の運用で肥大化しやすく、以下のDISMコマンドで最適化します。

# コンポーネントストアの分析
Dism /Online /Cleanup-Image /AnalyzeComponentStore

# 古いコンポーネントのクリーンアップ
Dism /Online /Cleanup-Image /StartComponentCleanup

# 旧バージョンを完全削除(ロールバック不可)
Dism /Online /Cleanup-Image /StartComponentCleanup /ResetBase

/ResetBase は古い更新のアンインストールを不可能にするため、月1回など計画的なタイミングでのみ実行するのが安全です。


Step 2: Tempフォルダクリーンアップの概要

$env:TEMPC:\Windows\Temp 配下には、インストーラーやアプリケーションが一時的に作成したファイルが蓄積します。全削除ではなく、「何日前より古いファイルを削除する」 というポリシーが現実的です。


Step 3: ログファイルローテーションの概要

ログディレクトリはアプリの数だけ増え、長期保存で容量を圧迫します。複数ディレクトリごとに「保持日数」を個別指定できる設計が望ましいため、PSD1設定ファイル によって柔軟に管理します。


Step 4: イベントログ保守の概要

イベントログは障害調査や監査に必須のため、頻繁にクリアすべきではありません。バックアップ(.evtx)取得後にクリアする運用を 月1回程度 実施するのが一般的です。

# バックアップとクリアの基本形
wevtutil epl System C:\Logs\System_20250101.evtx
wevtutil cl System

Step 5: スクリプト設計方針

本記事では、目的ごとに以下の4つのスクリプトを用意します。

  1. cleanup_temp.ps1
    Tempフォルダの古いファイル削除(削除日数をパラメーター指定)

  2. rotate_logs.ps1
    ログローテーション設定ファイル(PSD1)の指定フォルダ配下の.logを削除

  3. optimize_winsxs.ps1
    WinSxS の /StartComponentCleanup /ResetBase と削減量記録

  4. maintain_eventlogs.ps1
    指定したイベントログのバックアップとクリア

この構成にすることで、タスクスケジューラから 用途別に個別実行 でき、日次/月次のスケジュールやポリシー変更にも柔軟に対応できます。

例では下記のフォルダ構造を想定しています。

C:\
└─ Maintenance\
   ├─ cleanup_temp.ps1               # Tempフォルダクリーンアップ(日次)
   ├─ 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 クリーンアップスクリプト

このスクリプトは、$TempPaths に指定したフォルダ配下から DaysToKeep 日より古いファイルとフォルダを再帰的に削除 し、処理結果をログファイルに記録します。
DaysToKeepLogPath はパラメーターで上書き可能です。

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
        }
    )
}

ログローテーションスクリプト

このスクリプトは、PSD1 から読み込んだ RotationTargets を順に処理し、指定パス配下の .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: 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 クリーンアップ(日次タスク登録例)

毎日 2:00 に cleanup_temp.ps1 を実行し、7日より古い Temp ファイルを削除 します。
タスクは <<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

ログローテーション(日次タスク登録例)

毎日 2:30 に rotate_logs.ps1 を実行し、PSD1 設定に基づいて複数ディレクトリの .log ファイルをローテーション します。
保持日数は 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 最適化(月次タスク登録例)

毎月 1 日の 3:00 に optimize_winsxs.ps1 を実行し、WinSxS の 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

イベントログ保守(月次タスク登録例)

毎月 1 日の 3:30 に maintain_eventlogs.ps1 を実行し、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最適化:月次、削減量ログ付き
  • イベントログ保守:月次、バックアップ+クリア