富士通システムズウェブテクノロジー Advent Calendar 2019 2日目の投稿です
記事の内容は全て個人の見解であり、執筆内容は執筆者自身の責任です。所属する組織は関係ありません。
Windows機内の一時ファイル置き場に沢山の不要ファイルが溜まってきたので
単純に削除するのではなく、せっかくだから今まで使ってこなかったPowerShellを使ってみようとしました。
その際、はまってしまったことを整理した記事です。
発生したトラブル
フォルダの作成に失敗します。
やりたいこと
不要なファイルが入っているフォルダを削除し、新しい同名のフォルダを作成します。
実行環境
Windows10 Enterprise バージョン1809
実行したスクリプト
$target = "C:\temp\Trash"
# 不要フォルダを削除する
if (Test-Path $target) {
Remove-Item $target -Recurse
}
# 新規にフォルダを作成する
New-Item $target -itemType Directory | Out-Null
エラーの詳細
以下の2つのエラーのどちらかが発生します。
後者はおそらく削除のためにロックをかけているのでしょう。
そのタイミングで作成しようとすると発生すると思われます。
C:\Scripts\ > .\Trash.ps1
New-Item : 指定された名前 C:\temp\Trash の項目は既に存在します。
発生場所 C:\Scripts\Trash.ps1:8 文字:1
+ New-Item $target -itemType Directory | Out-Null
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (C:\temp\Trash:String) [New-Item], IOException
+ FullyQualifiedErrorId : DirectoryExist,Microsoft.PowerShell.Commands.NewItemCommand
C:\Scripts\ >
C:\Scripts\ > .\Trash.ps1
New-Item : アクセスが拒否されました。
発生場所 C:\Scripts\Trash.ps1:8 文字:1
+ New-Item $target -itemType Directory | Out-Null
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (C:\temp\Trash:String) [New-Item], UnauthorizedAccessException
+ FullyQualifiedErrorId : ItemExistsUnauthorizedAccessError,Microsoft.PowerShell.Commands.NewItemCommand
C:\Scripts\ >
原因
PowerShellのRemove-Item
コマンドは、別プロセスで実行されるらしく、
削除完了を待たずに、次の処理に移行してしまいます。
すると、C:\temp\Trash
の削除が完了する前にC:\temp\Trash
を作成しようとするため
今回のトラブルが発生しました。
対応方法
対応方法は大きく3つ思いつきました。
- 削除完了を待ってから作成を行う
- 削除対象を別名で退避し、作成を行う
- 外部アプリを使う。
別途削除アプリを作り、PowerShellから待ち受けで実行すればできますが、
それは今回の目的から外れるので1と2を試しました。
「削除完了を待ってから作成を行う」場合
Remove-Item
コマンドを実行後、Start-Sleep
コマンドを使い何秒か経過してから
New-Item
コマンドを実行しても良いのですが、適切なスリープ時間を決めることは難しいです。
そこで、C:\temp\Trash
が存在しているかどうかをチェックし、
存在しなくなったら作成するようにしたのが、次のスクリプトです。
$target = "C:\temp\Trash"
# 不要フォルダを削除する
if (Test-Path $target) {
Remove-Item $targetBk -Recurse
}
# 対象の存在チェックを繰り返す
while (Test-Path $target) {
# まだ存在しているので1秒待ってから再度チェックを行う
Start-Sleep -Seconds 1
}
# 新規にフォルダを作成する
New-Item $target -itemType Directory | Out-Null
削除される対象の量の大小によって、Sleep時間は増減させればいいでしょう。
ただし、このスクリプトにはトラブルが発生しました。
whileのTest-Path
の際に、アクセスエラーが発生することがあるのです。
原因はわかりません。
「削除対象を別名で退避し、作成を行う」場合
Rename-Item
もしくはMove-Item
コマンドで対象をリネームすることで
削除処理の影響をなくしたのが、次のスクリプトです。
$target = "C:\temp\Trash"
# 不要フォルダを削除する
if (Test-Path $target) {
$targetBk = $target + "Bk"
# 一度退避してから削除する
Rename-Item $target $targetBk
Remove-Item $targetBk -Recurse
}
# 新規にフォルダを作成する
New-Item $target -itemType Directory | Out-Null
フォルダを作成と削除を分けることでトラブルを回避します。
先の「削除完了を待ってから作成を行う」と異なり、こちらは安定して動作しました。
まとめ
もともと削除処理は結果を待ち受けません。
それを無理に待ち受ける処理にするよりも
その動作をそのまま受け入れた方式の方が自然に動作するようです。