forfiles.exe /D -90 で「90日より古いファイル」を削除するつもりが、昔作ったファイルをコピーした直後に消えてしまうことがあります。原因は forfiles が作成日時ではなく最終更新日時を基準に判定するためです。仕組みの整理から、PowerShellで作成日時ベースに切り替える方法、安全に運用するコツまでまとめます。
現象:コピー直後のファイルが forfiles /D -90 で削除対象になる
Windows の定期クリーンアップでよく使われるのが forfiles.exe です。例えば、次のようなバッチで「90日より古いファイルを削除」しているケースは多いと思います。
"%SystemRoot%\System32\forfiles.exe" /P "C:\Folder" /S /M *.* /D -90 /C "cmd /c del /q @path"
ところが、昔作ったファイルを後から C:\Folder にコピーしただけなのに、コピー直後にもかかわらず削除されてしまうことがあります。特にアーカイブ用途で「古い資料をまとめてコピーした」ようなときに起きやすく、原因が分からないとかなり怖い挙動に見えます。
このとき dir /T:C(作成日時表示)で確認すると、コピー先では作成日時が新しく見えるため、
- 「作成日時が新しいのに、なぜ古い判定になるのか」
- 「forfiles は別の日時を見ているのでは?」
という疑問につながります。
結論:forfiles.exe は「作成日時」ではなく「最終更新日時」で判定する
結論から言うと、forfiles.exe の /D 判定は “最終更新日時(LastWriteTime / Date modified)” が基準です。つまり、コピー先の作成日時(CreationTime)が新しくても、元ファイルの最終更新日時が古いままなら、/D -90 では「90日より古い」と見なされ、削除対象になります。
これを理解すると、現象がきれいに説明できます。
- エクスプローラーでコピー → コピー先では「作成日時」は“今”になりやすい
- しかし「最終更新日時」は元ファイルの値を引き継ぐ(=昔のまま)
- forfiles は最終更新日時で比較 → “古い” と判定して削除
逆に言えば、「最後に編集されてから90日経ったファイルを消したい」(長期間更新されていないログや一時ファイルを削除したい)という目的なら、forfiles の挙動は仕様どおりで、むしろ都合が良いこともあります。
ファイルのタイムスタンプを整理する(混同しやすいポイント)
Windows のファイルには複数のタイムスタンプがあり、どれを見ているかで結果が変わります。混同を避けるため、よく使う3種類を整理します。
| 種類(日本語) | エクスプローラーの列名(例) | PowerShell のプロパティ | dir の表示 | 主な意味 | コピー時の挙動(一般的) |
|---|---|---|---|---|---|
| 作成日時 | 作成日時 / Date created | CreationTime | dir /T:C | その場所(ボリューム)でファイルが作られた日時 | エクスプローラーの通常コピーでは「コピーした時刻」になりやすい |
| 最終更新日時 | 更新日時 / Date modified | LastWriteTime | dir /T:W(既定表示もこれ) | 内容が最後に変更(保存)された日時 | コピー元の値を引き継ぎやすい(昔のままになりやすい) |
| 最終アクセス日時 | アクセス日時 / Date accessed | LastAccessTime | dir /T:A | 最後に読み取り等でアクセスされた日時 | 環境設定次第で更新されない/粗い更新になることがあるため運用判断に注意 |
今回のポイントは、forfiles /D は LastWriteTime(更新日時)を見ているという点です。作成日時での整理・削除を期待している場合、ここがズレます。
「dir /T:C では新しい」のに削除される理由
dir の既定表示は多くの環境で「最終更新日時」を出しています。一方で dir /T:C を付けると「作成日時」を出します。つまり、同じファイルでも見ている列が違うだけで印象が変わります。
また、エクスプローラーも既定では「更新日時」を表示していることが多いです。列を「作成日時」に切り替えると、コピー直後で新しく見える一方、更新日時の列では古いまま、という状態が発生します。
その結果、次のような “勘違い” が起きやすくなります。
- コピーした=新しいファイルになった(と思い込む)
- しかし “更新日時” は新しくならない
- forfiles は更新日時で比較する
- コピー直後でも “古い” と判定される
手元で再現して仕組みを確認する(検証コマンド集)
運用前に一度、検証しておくと安心です。以下は「更新日時が古いファイルをコピーすると削除対象になる」流れを最小構成で確認する例です。
更新日時が古いファイルを用意する
rem 作業用フォルダー
mkdir C:\Temp\Src
mkdir C:\Temp\Dst
rem ファイル作成
echo test > C:\Temp\Src\old.txt
rem 更新日時を過去に戻す(PowerShellで設定)
powershell -NoProfile -Command "(Get-Item 'C:\Temp\Src\old.txt').LastWriteTime = (Get-Date).AddDays(-200)"
コピーして、作成日時と更新日時を見比べる
copy C:\Temp\Src\old.txt C:\Temp\Dst\
rem 作成日時(CreationTime)
dir C:\Temp\Dst\ /T:C
rem 更新日時(LastWriteTime)
dir C:\Temp\Dst\ /T:W
多くの環境では、コピー先で以下のような状態になります。
| 項目 | コピー先での見え方 | 意味 |
|---|---|---|
| 作成日時 | コピーした“今”の時刻 | コピー先でファイルが作られたので新しい |
| 更新日時 | 元ファイルの“過去”の時刻 | 中身を最後に編集した時刻は変わらない |
forfiles がどの日時で判定しているかを確認する
いきなり削除せず、まずは対象ファイルを出力して確認します。
"%SystemRoot%\System32\forfiles.exe" /P "C:\Temp\Dst" /M *.* /D -90 /C "cmd /c echo HIT: @path @fdate @ftime"
@fdate / @ftime は forfiles が参照している日時(=最終更新日時)を表します。ここに “過去日付” が出ていれば、/D -90 にヒットするのは自然な結果です。
forfiles を使い続ける場合の安全な運用
forfiles は軽量でバッチから呼びやすく、ログ削除などで非常に便利です。ただし削除コマンドは事故につながりやすいので、次の2段階運用を強くおすすめします。
まずは「削除候補だけ表示」して確認する
"%SystemRoot%\System32\forfiles.exe" /P "C:\Folder" /S /M *.* /D -90 /C "cmd /c echo @path"
この出力に「消してはいけないファイル」が混ざっていないかを確認します。特に今回のように “コピー直後に消える” といった想定外がある場合、いきなり del しないだけで事故確率が一気に下がります。
問題なければ削除コマンドに切り替える
"%SystemRoot%\System32\forfiles.exe" /P "C:\Folder" /S /M *.* /D -90 /C "cmd /c del /q @path"
削除対象をさらに絞りたい場合は、/M に拡張子(例:*.log)を指定すると管理しやすくなります。
"%SystemRoot%\System32\forfiles.exe" /P "C:\Logs" /S /M *.log /D -90 /C "cmd /c del /q @path"
forfiles の置換変数を知っておくと事故防止に役立つ
forfiles は実行時に使える変数があり、出力や条件分岐に活用できます。
| 変数 | 内容 | 例 | 実務での使いどころ |
|---|---|---|---|
| @file | ファイル名(拡張子含む) | app.log | ログにファイル名だけ残したい |
| @path | フルパス | C:\Logs\app.log | 削除や移動など“実処理”に使う |
| @relpath | 相対パス | \2025\app.log | フォルダー構成を保ったまま別場所へ退避したい |
| @fdate | ファイル日付 | 2025/10/01 | 対象になった理由(更新日時)をログに残す |
| @ftime | ファイル時刻 | 12:34 | 同上 |
| @fsize | サイズ(バイト) | 1048576 | 一定サイズ以上だけ削除などに応用 |
| @isdir | ディレクトリかどうか | TRUE / FALSE | フォルダーを誤って処理しないためのガード |
例えば「ディレクトリは除外してファイルだけ削除したい」なら、次のような書き方もできます。
"%SystemRoot%\System32\forfiles.exe" /P "C:\Folder" /S /M *.* /D -90 /C "cmd /c if @isdir==FALSE del /q @path"
作成日時で判定したいなら PowerShell が確実
「コピー先で作られた(=作成日時が新しい)ファイルは消したくない」「フォルダーへ取り込んだ日付で世代管理したい」という要件なら、forfiles ではなく PowerShell で CreationTime を基準に絞り込みます。
基本形:90日より前に作成されたファイルを削除
Get-ChildItem -Path "C:\Folder" -File |
Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-90) } |
Remove-Item -Force
まずは削除せず、一覧で確認する版を用意しておくと安全です。
Get-ChildItem -Path "C:\Folder" -File |
Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-90) } |
Select-Object FullName, CreationTime, LastWriteTime, Length |
Sort-Object CreationTime
サブフォルダーも対象にする(-Recurse)
Get-ChildItem -Path "C:\Folder" -File -Recurse |
Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-90) } |
Remove-Item -Force
本番運用に強い形:WhatIf とログ出力
定期実行(タスクスケジューラ)に載せるなら、「候補のログを残す」「初回は WhatIf で挙動確認」の2点が効きます。
$target = "C:\Folder"
$limit = (Get-Date).AddDays(-90)
$log = "C:\Logs\cleanup_candidates.csv"
$candidates = Get-ChildItem -Path $target -File -Recurse |
Where-Object { $_.CreationTime -lt $limit } |
Select-Object FullName, CreationTime, LastWriteTime, Length
$candidates | Export-Csv -Path $log -NoTypeInformation -Encoding UTF8
# 初回は -WhatIf で確認(問題なければ外す)
$candidates | ForEach-Object { Remove-Item -LiteralPath $_.FullName -Force -WhatIf }
-LiteralPath を使うと、角括弧 [ ] などワイルドカード扱いされる文字を含むパスでも事故が起きにくくなります。
目的別:更新日時ベースと作成日時ベース、どちらが正解?
「どっちを使うべきか」は目的で決まります。運用要件に合わせて基準を選ぶのがポイントです。
| やりたいこと | おすすめ基準 | 理由 | 代表的な実装 |
|---|---|---|---|
| 最後に更新されてから長いファイルを掃除したい(ログ、キャッシュ) | 最終更新日時(LastWriteTime) | 「触っていない期間」を表せる | forfiles /D -90 |
| フォルダーに取り込んだ(保管した)日付で世代管理したい | 作成日時(CreationTime) | “コピーした日” で管理できる | PowerShell で CreationTime を判定 |
| アクセスされていないものを消したい | 最終アクセス日時(LastAccessTime) | 読み取りの有無を追える可能性 | ただし設定次第で更新されないため要検証 |
運用でつまずきやすい落とし穴と対策
コピー方法によっては「作成日時」が期待どおりにならないことがある
エクスプローラーの通常コピーでは「コピー先の作成日時が今になる」ケースが多い一方、バックアップツールや robocopy のオプション次第では タイムスタンプを保持してコピーすることがあります。作成日時ベースで運用したい場合は、実際の取り込み手順で CreationTime がどうなるかを Get-Item などで必ず確認してください。
「移動」と「コピー」でタイムスタンプの残り方が変わる
同じボリューム内の移動は“実体の移動”ではなく“参照の付け替え”に近いため、作成日時が変わらない場合があります。整理の自動化をするなら、運用が「コピー」なのか「移動」なのかを明確にし、どの日時を基準にするかを決めておくのが安全です。
forfiles で “コピー直後に消える” のを避けたいが、基準は更新日時のままにしたい場合
「実は更新日時で掃除したい(長期間編集されていないものを消したい)けれど、コピー直後に消えるのは困る」というケースもあります。この場合は運用の整理が必要です。
- コピーした瞬間に“掃除対象”になるのは、更新日時が古いから
- つまり「取り込んだら一定期間は保持したい」なら、取り込み日時を別の仕組みで管理する必要がある
現実的な回避策としては、次のいずれかです。
- 取り込み後に「更新日時を現在に更新」して、一定期間は残す(※更新日時ベースの意味が変わる点に注意)
- 削除対象フォルダーと“保管直後フォルダー”を分け、一定期間経ったら移動する
- 作成日時ベース(PowerShell)に切り替える
更新日時を現在にそろえる(いわゆる touch)例:
Get-ChildItem -Path "C:\Folder" -File |
ForEach-Object { $_.LastWriteTime = Get-Date }
この方法は手軽ですが、「実際に最後に編集した日」が失われるため、監査や証跡が必要なフォルダーでは慎重に判断してください。
ネットワーク共有・NAS では時刻や権限の影響を受けやすい
ファイルサーバーやNAS上の共有では、クライアントとサーバーの時刻差、タイムゾーン、権限(削除権限・所有権)などが絡みます。まずは候補表示(echo)で対象が期待どおりか、削除が失敗していないかをログで追えるようにしておくと安心です。
「まず表示して確認する」文化が一番の事故防止
自動削除は便利ですが、誤削除は復旧コストが高くなりがちです。運用としては次をセットにすると強いです。
- 初回は必ず “削除候補の表示” を行い、想定外がないかを見る
- 削除対象フォルダーを限定し、いきなりルート直下などを指定しない
- ログ(削除したファイルのパスと日時)を残す
- 可能なら「退避→一定期間後に完全削除」の2段階にする
よくある質問(FAQ)
Q. dir /T:C で作成日時が新しいのに、forfiles が古いと判断するのはなぜ?
A. forfiles /D は作成日時ではなく最終更新日時(LastWriteTime)で判定するためです。dir /T:W や PowerShell の LastWriteTime で見ると、forfiles と同じ基準で確認できます。
Q. forfiles で作成日時を基準にするオプションはありますか?
A. 標準の forfiles には作成日時ベースのフィルター指定がありません。作成日時でやりたい場合は PowerShell の Get-ChildItem と CreationTime で絞り込むのが確実です。
Q. 「コピーした日から90日」で消したいです。何を基準にすればいい?
A. エクスプローラーの通常コピーであれば作成日時(CreationTime)がコピー日になりやすいので、PowerShell で CreationTime 判定にするのが分かりやすいです。ただし、コピー方法によっては作成日時が維持される場合もあるため、取り込みフローで実際にどうなるか確認してから運用に載せてください。
Q. どちらにしても「うっかり消してしまう」のが怖いです
A. いきなり削除せず、まずは候補をログに出す運用にすると安全です。forfiles なら del の代わりに echo にする、PowerShell なら -WhatIf を使う、という形で“無害なドライラン”を挟むだけでも事故率は大きく下がります。
まとめ:forfiles の判定基準を押さえると「消えた」の原因が追える
コピー直後のファイルが forfiles /D -90 で削除される問題は、ツールの不具合ではなく判定に使う日時の違いで説明できます。最後に要点だけ整理します。
- forfiles /D は最終更新日時(LastWriteTime)を基準に古さを判定する
- コピー先で作成日時が新しく見えても、更新日時が古ければ削除対象になり得る
- 作成日時ベースで削除したいなら PowerShell(CreationTime で Where-Object)に切り替える
- 削除は必ず「候補の表示(ドライラン)→本削除」の2段階で、ログも残す
一度タイムスタンプの基準を揃えて確認できるようになると、Windows のファイル整理は格段に安定します。安全第一で、自分の運用目的に合った基準(更新日時/作成日時)を選んでください。

コメント