はじめに
この記事は身の回りの困りごとを楽しく解決! by Works Human Intelligence Advent Calendar 2025 15日目の記事です。
前回の記事でファイル一覧を作成したのですが、作ってから「ファイルの削除も追加してくれ」と指示を受けました。
心の中で「最初にこっちが提案して『いらん』言うてたやろ!」と思いましたが、新人ですので満面の笑顔で了承しました。
要件
要件は以下の内容になります。
・前回作成したプログラムで取得したファイルを削除
・削除したら別途ログファイルで一覧出力してほしい
・・・先輩がおっしゃる要件は雑な気がしたので、自分なりにこうしました。
・前回作成したプログラムで取得したファイルを人力で確認する。
・確認後、削除してはいけないファイルはログファイルから削除する(人力)
・削除作業後のファイルを参照し、中に記載されているファイルは削除する。
・新規作成したログファイルに削除が完了したらファイルパスを記載する。
・何かしらの原因で削除できなかった場合は、新規作成したログファイルの最後にまとめて記載する。
先輩に確認したら「それで良いよ」と言われたので構築します。(中身を見てはいないと思いますけどね!)
プログラム作成
今回も.batと.ps1で作成していきます。
お客様の環境で実行するらしいので、ダブルクリックで実行できる.batを用意します。
基本的なプログラムそのものはPowerShellで実行していきます。
@echo off
echo Start Programs. Please wait...
@REM スクリプトパス
SET SCRIPT_PATH=".\\files\\DeleteFiles.ps1"
@REM PowerShellの実行許可を与えて実行
powershell.exe -ExecutionPolicy ByPass -File %SCRIPT_PATH%
echo Finished Programs. Please check the log file.
pause
$TargetFilePathArray = (
".\\result.txt"
)
function Delete-File($SearchPath) {
# ファイルパスからファイル名を取得
# 取得したファイル名からDeleted用のログファイル名を作成
$FileName = Split-Path $SearchPath -Leaf
$DeleteLogFileName = "Deleted_" + $FileName
# 削除に失敗したファイルパスを管理するための配列
$FailDeletePath = @()
# ファイルパスから内容を取得し、1行ずつ処理する
$lines = Get-Content -Path $SearchPath
for ($i = 0; $i -lt $lines.Count; $i++) {
# 最初の3行は別の内容が記載される予定なのでスキップ
if (($i -eq 0) -or ($i -eq 1) -or ($i -eq 2)) {
continue
}
# 各行の内容を特定の文字で分割し、5番目が削除対象のファイルパスになる
$line = $lines[$i]
$parts = $line -split " | "
$TargetPath = $parts[5]
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
# ファイルパスが存在するか確認、なければ配列FailDeletePathに追加
if (Test-Path $TargetPath) {
# ファイル削除実行、削除ができればその内容をログファイルに記載
# 削除できなければ配列FailDeletePathに追加
try {
Remove-Item $TargetPath -Force
Add-Content -Path $DeleteLogFileName -Value "[${timestamp}]Deleted: $TargetPath"
}
catch {
$FailDeletePath += "[${timestamp}]Failed to delete: $TargetPath"
}
}
else {
$FailDeletePath += "[${timestamp}]Invalid path: $TargetPath"
}
}
# 配列FailDeletePathが1つ以上要素があれば順番に内容を追記する。
if ($FailDeletePath.Count -gt 0) {
Add-Content -Path $DeleteLogFileName -Value "`n--- Failed Operations ---"
$FailDeletePath | ForEach-Object {
Add-Content -Path $DeleteLogFileName -Value $_
}
}
}
for ($i = 0; $i -lt $TargetFilePathArray.Count; $i++) {
Delete-File -SearchPath $TargetFilePathArray[$i]
}
プログラムの説明
batファイル
基本的に前回の記事からの流用です。
重複内容で文字数が増えるのは嫌なので省略させていただきます。
ps1ファイル
$TargetFilePathArray = (
".\\result.txt"
)
function Delete-File($SearchPath) {
・・・
}
for ($i = 0; $i -lt $TargetFilePathArray.Count; $i++) {
Delete-File -SearchPath $TargetFilePathArray[$i]
}
今回は前回とは異なり、関数を用いています。
基本的な流れとしては、最初に参照すべきログファイルを配列として格納する部分を記載。
その後に関数の設定と繰り返し処理によって配列の中身のファイルごとに関数を実行する流れになります。
function Delete-File($SearchPath) {
# ファイルパスからファイル名を取得
# 取得したファイル名からDeleted用のログファイル名を作成
$FileName = Split-Path $SearchPath -Leaf
$DeleteLogFileName = "Deleted_" + $FileName
# 削除に失敗したファイルパスを管理するための配列
$FailDeletePath = @()
・・・
}
コメントアウトにほとんど書いてしまっているのですが、補足します。
$lines はあくまで1行ごとを配列に格納した形です。
その後繰り返し処理によって1行ずつ実行していきます。
前回の内容でログファイルの最初にヘッダーを作成しているので、その部分のみはスキップします。
また記載したログの中で必要となるのはパスのみなので、特定の文字で区切りパスの部分以外は無視します。
このあたりの設定に関しては各々の作成する前提内容によって変更してください。
・・・
# ファイルパスから内容を取得し、1行ずつ処理する
$lines = Get-Content -Path $SearchPath
for ($i = 0; $i -lt $lines.Count; $i++) {
# 最初の3行は別の内容が記載される予定なのでスキップ
if (($i -eq 0) -or ($i -eq 1) -or ($i -eq 2)) {
continue
}
# 各行の内容を特定の文字で分割し、5番目が削除対象のファイルパスになる
$line = $lines[$i]
$parts = $line -split " | "
$TargetPath = $parts[5]
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
・・・
}
・・・
かつてはそのパスが存在したが、フォルダ名の変更や削除によってパスが無くなっていることもあり得ます。
そこで念のためにそのファイルやパスが存在するかを確認します。
Test-Pathはテストを行った結果をTrue・Falseで返してくれるので、if文の中に直接含めています。
削除の可否にもよりますが、実行時間と実行結果をログファイルとして新しく作成したファイルに記載します。
本格的なプログラムを作成するのであれば、もっと複数のエラー理由などに分けるべきでしょうが、今回は「特定のお客様のため」に「追加費用無し」で作成しているプログラムなので我慢していただきましょう。
・・・
# ファイルパスが存在するか確認、なければ配列FailDeletePathに追加
if (Test-Path $TargetPath) {
# ファイル削除実行、削除ができればその内容をログファイルに記載
# 削除できなければ配列FailDeletePathに追加
try {
Remove-Item $TargetPath -Force
Add-Content -Path $DeleteLogFileName -Value "[${timestamp}]Deleted: $TargetPath"
}
catch {
$FailDeletePath += "[${timestamp}]Failed to delete: $TargetPath"
}
}
else {
$FailDeletePath += "[${timestamp}]Invalid path: $TargetPath"
}
・・・
最後にエラーが発生したファイルが一つでもあればその内容を時間順に記載します。
時間順に記載するというのであれば、時間でソートなどをすべきに思えますが、FIFOということで笑
・・・
# 配列FailDeletePathが1つ以上要素があれば順番に内容を追記する。
if ($FailDeletePath.Count -gt 0) {
Add-Content -Path $DeleteLogFileName -Value "`n--- Failed Operations ---"
$FailDeletePath | ForEach-Object {
Add-Content -Path $DeleteLogFileName -Value $_
}
}
・・・
最後に
プログラムの作成自体は苦では無いのですが、普段プログラムの書かない人たちに引き継ぐのが面倒です。
問題があったときに、私以外でも対応できるようにしたいのでしょうけども多分そのときにはこのプログラムの存在を忘れていると思います。
私ですら忘れるのですから。