none
フォルダの自動削除バッチの仕様について RRS feed

  • 質問

  • 顧客の依頼で、特定のPCのDドライブに対し古いファイルを自動削除する仕組みを導入してほしいと言われており、バッチをタスクスケジューラで1日ごとに実行する方向で考えているのですが、バッチを作成するにあたって少々詰まっております。

    要件としては「更新日時が3ヶ月以上前のファイルを削除、ただし削除対象でないファイルが1つでも存在する階層が存在した場合、同階層の他ファイルは削除しない」といったものです。
    具体的には下記のイメージです。

    例:2020/12/20にバッチを実行した場合
    パターン①
    AAAフォルダ
     ├aaa.txt(更新日時:2020/8/20)
     ├bbb.txt(更新日時:2020/8/20)
     └ccc.txt(更新日時:2020/8/20)

           

    中のファイル全て最終更新日時が4ヶ月前なので中のファイル全て削除される

    パターン②
    AAAフォルダ
     ├aaa.txt(更新日時:2020/11/20)
     ├bbb.txt(更新日時:2020/8/20)
     └ccc.txt(更新日時:2020/8/20)

            

    aaa.txtは更新日時が「2020/11/20」で更新日時が1ヶ月前となっており削除要件を満たしていないため、結果、同階層のbbb.txtとccc.txtは古い更新日時でも削除されない。

    このような条件を満たすスクリプトを作成することは可能でしょうか。もしあるならご教授頂きたく。

    よろしくお願いします。




    • 編集済み shikkoku 2021年1月27日 0:52
    2021年1月26日 9:28

すべての返信

  • © ウィンドウズスクリプトプログラマ - Windows Script Programmer 2020

    Windowsの仕様ではファイルの更新日時が変わるとそのファイルが格納されてるフォルダの更新日時も併せて更新されるので
    そういう仕様はないので、やめたほうがいいでしょう。

    2021年1月26日 12:36
  • 中のファイルが更新されるとフォルダも併せて更新されると思っていたのですが違うんですね。

    すいません、私の認識が誤っておりました。再度考え直してみます。

    ありがとうございます。

    ※投稿内容を編集させて頂きました。再度アドバイスを頂けると幸いです。


    • 編集済み shikkoku 2021年1月27日 0:43
    2021年1月27日 0:18
  • 単純に指定のファイルを削除する、フォルダの配下のファイルが0になったらそのフォルダを削除するではだめなのですかね。

    条件をよく読んでなかったですね。すみません。

    • 編集済み Mr.Spock 2021年1月28日 2:12
    2021年1月27日 8:22
  • >ただし削除対象でないファイルが1つでも存在する階層が存在した場合、同階層の他ファイルは削除しない
    なので削除しては要件みたさないですね。
    フォルダ構成がどのようになっているのかわかりませんが、フォルダ毎にフラグとなる変数を設けて、
    「残す対象となるファイルの有無」で判断して残すファイルが一つも存在しない場合はフォルダごと削除。
    といった形になるかと思います。
    上記処理を関数化して、最上位のフォルダからディレクトリのみを検索してForeachでその関数に渡していくとかでどうでしょうか。
    2021年1月28日 0:16
  • バッチファイル内からPowerShellスクリプトを呼び出すのは有りなのでしょうか?

    例えばbatファイルのほかに<PowerShellScript>.ps1を用意します。

    <PowerShellScript>.ps1の中身はざっくりと以下の感じです。

    ==================================ここから
    if( (Get-ChildItem "<チェック対象フォルダパス>" | Where-Object {$_.LastWriteTime -ge (Get-Date).AddMonths(-3)}).Count -ge 1){
        #パターン②のルート
        #<チェック対象フォルダパス>に対し現在の時刻から3か月前より新しい更新日付のファイルが1以上ある場合のルート(更新日付が3か月以内のものが1つ以上ある)
        $exit_Code = 1
    }
    else{
        #パターン①のルート
        #<チェック対象フォルダパス>に対し現在の時刻から3か月前より新しい更新日付のファイルが1つもない場合のルート(すべてのファイルの更新日付が3か月前より古い)
        $exit_Code = 0
    }

    exit $exit_Code 
    ==================================ここまで

    でバッチファイルから<PowerShellScript>.ps1を呼び出しエラーレベル(実行結果)を取得して1なら何もしない。0ならすべて削除する

    powershell.exe -ExecutionPolicy RemoteSigned -File ".\<PowerShellScript>.ps1"
    set resultcode=%ERRORLEVEL% 

    といった感じはどうでしょうか?条件にバッチファイルのみで完結させる必要がある場合は使用できませんが、Powershellを

    使用できるのであれば条件抽出の部分が簡単になりそうな感じはしています。




    • 編集済み Savior13 2021年1月29日 5:18
    2021年1月28日 5:13
  • © ウィンドウズスクリプトプログラマ - Windows Script Programmer 2020

    for /r . /d %%I in (*) do forfiles /p %%I /d +2021/01/31 || del %%I /q

    かな。
    2021年1月30日 11:46
  • 自分の環境で試行してみたところ、ファイルの更新に伴ってフォルダの更新日付も変更されましたが。
    1.フォルダ内のファイルを直接編集して更新
    2.他のフォルダで更新したファイルをコピー
    3.フォルダ内のファイルを削除

    上記3つのパターンですべてフォルダのLastWriteTimeが更新されます。
    2021年2月1日 2:14
  • © ウィンドウズスクリプトプログラマ - Windows Script Programmer 2020
    2,3は当然。directory entryのプライマリキーが変化するようなものは、更新日時が変わる。

    メモ帳で開いて、上書き保存すると、ファイルの更新日時は変わるが、ディレクトリの更新日時は変わらない。

    ファイルの更新日時を直接書き換えても、ディレクトリの更新日時は変わらない。

    フォルダの更新日時更新されない。 - マイクロソフト コミュニティ (microsoft.com)
    2021年2月2日 2:21
  • $exit_Code=0で抜けるのではなくそのまま対象フォルダを削除する処理を入れればそのまま完結できませんかね?

    2021年2月2日 7:31
  • 返信が遅くなってしまい申し訳ありません。

    皆様ご回答ありがとうございます。

    既にご指摘があったように説明が不足してたので補足させて頂きます。

    ・バッチ処理の対象範囲はDドライブ直下のフォルダのサブフォルダから再帰的に処理する。

    つまり下記の例でいくと「hoge1」フォルダは処理対象外で、「AAA」フォルダからサブフォルダ含めて再帰的に処理を行う想定です。


    Dドライブ

     └hoge1フォルダ →処理対象外

       └AAAフォルダ →処理対象

         └BBBフォルダ →処理対象


    ・バッチ処理の際、フォルダ内が空の場合はフォルダごと削除する

    これは逆に言うとフォルダ内のファイルが全て削除されたとしても中にサブフォルダが存在したら、フォルダは削除されないということになります。

    Dドライブ

     └hoge1フォルダ

       └AAAフォルダ

         └aaa.txt →削除対象

         ├bbb.txt →削除対象

         └BBBフォルダ


    上記の場合だと、「aaa.txt」と「bbb.txt」削除されても、AAAフォルダのサブフォルダであるBBBフォルダは存在してるので、AAAフォルダは削除されないようにしたいです。

    となると、AAAフォルダの最下層から再帰的に処理する仕組みが必要なのかなと考えております。

    使用する言語にこだわりはなくPowerShellでも全然構いません。

    申し訳ありませんがご教授のほどよろしくお願いします。


    • 編集済み shikkoku 2021年2月3日 8:33
    2021年2月3日 8:28
  • Set-location D:\hoge1

    foreach ($Target in (Get-Childitem -Recurse))  {
    if ((Get-Childitem -path $Target |Where-Object{($_.Lastwritetime -ge (get-date).AddMonths(-3)) -or ($_.mode -like 'd*')}).count -lt 1)
     {
         Remove-Item $Target.Fullname -recurse -force
     }

    }

    こんな感じ?※一部Savior13さん記載の条件をそのまま流用しています。
    実際には削除処理を含むのでバックアップを取得したうえでのテストをしてください。削除したフォルダ名をログに残したい場合は、Remove処理の前後で$Target.Name.ToString()を出力してください。


    • 編集済み チキン 2021年2月3日 23:48
    2021年2月3日 8:59
  • © ウィンドウズスクリプトプログラマ - Windows Script Programmer 2020



    となると、AAAフォルダの最下層から再帰的に処理する仕組みが必要なのかなと考えております。
    それは、こう。


    call :sub .
    rem pause
    goto :eof
    :sub
    for /d %%I in ("%~1\*") do (
    call :sub "%%~I"
    forfiles /p "%%~I" /d +2021/01/31 || del "%%~I" /q 
    rd "%%~I"
    )


    バッチでネックなのは、

    更新日時が3ヶ月以上前
    これが簡単に計算できないこと。スクリプトに頼るしかない。なら全部スクリプトでやればよい。チャンチャン。
    2021年2月3日 14:45
  • さらなるアドバイス誠にありがとうございますm(_ _)m

    提案頂いた方法をベースにスクリプトを作成して、まずはテスト環境で試してみようと思います。

    皆様、この度は本当にありがとうございました。

    2021年2月8日 4:16