😎

【PowerShell】一時フォルダで作業して自動で後片付けする

3 min read

環境・コード

> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      7.1.3
PSEdition                      Core
GitCommitId                    7.1.3
OS                             Microsoft Windows 10.0.19042
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
function Use-TempDir {
    param (
        [ScriptBlock]$script
    )
    $tmp = $env:TEMP | Join-Path -ChildPath $([System.Guid]::NewGuid().Guid)
    New-Item -ItemType Directory -Path $tmp | Push-Location
    "working on tempdir: {0}" -f $tmp | Write-Host -ForegroundColor DarkBlue # 不要ならコメントアウト
    $result = Invoke-Command -ScriptBlock $script
    Pop-Location
    $tmp | Remove-Item -Recurse
    return $result
}

一時ファイルの作成方法は こちら を参考にしました。

経緯

PowerShell で一時ファイルを作ってゴニョゴニョするとしたら、候補として上がるのはバージョン5.0から導入された New-TemporaryFile かと思います。

しかしこのコマンドレットは、

  • 一時フォルダは作れない
  • 後処理は手動(削除処理を都度書かなくてはいけない)

といった点で使いにくいと感じる方は多いのではないでしょうか。

今回作ったコマンドレットは、 一時フォルダ作成 → 一時フォルダに移動 → 処理を実行して結果を返す → 一時フォルダ削除 という流れを自動化するものです。

使用例

外部プログラムに文字列を渡して処理したい場合などに便利です。


# コマンドレット定義
function Invoke-Python {
    $lines = @($input | Out-String -Stream) # パイプ経由で渡された内容を文字列配列として格納しておく
    $pyCodePath = $PSScriptRoot | Join-Path -ChildPath "python\proc.py"

    # 事前に定義しておいた Use-TempDir を呼び出す
    Use-TempDir {
        $tmpIn = New-Item -Path ".\tmpIn.txt"
        $tmpOut = New-Item -Path ".\tmpOut.txt"
        $lines | Out-File -Encoding utf8NoBOM -FilePath $tmpIn
        'python "{0}" "{1}" "{2}"' -f $pyCodePath, $tmpIn.Fullname, $tmpOut.Fullname | Invoke-Expression
        Get-Content $tmpOut | Write-Output
    }

}

# 呼び出し
cat .\hogehoge.txt | Invoke-Python

素直に考えればコマンドライン引数を使うケースではありますが、特殊文字のエスケープや空白の処理がプログラム間で異なるため面倒になることが大半です。

色々と試行錯誤した結果、「一時ファイルに文字列を書き出してから一時ファイルのパスだけを外部プログラムに渡す」という書き方をするようになり、もう少し楽に書くために今回のコマンドレットに思い至った次第です。

注意

> Use-TempDir { echo $pwd.Path }
Microsoft.PowerShell.Core\FileSystem::C:\Users\(ユーザー名)\AppData\Local\Temp\~~~~~~~~~~

> Use-TempDir { echo $pwd.ProviderPath }
C:\Users\(ユーザー名)\AppData\Local\Temp\~~~~~~~~~~

渡したスクリプトブロック内で $PwdGet-LocationPath プロパティで作業中の一時フォルダ名を取得しようとすると、 Microsoft.PowerShell.Core\FileSystem:: という文字列がパスの先頭に追加されます。

これは PowerShell のプロバイダの概念によるもので、ファイルシステム内を処理していることを明示的に示すために表示されるようです。単にパスのみ必要な場合は ProviderPath プロパティを使用すればOKです。

PowerShell にとってのプロバイダ

PowerShell の cdSet-Location) や lsGet-ChildItem)はファイルシステムに限らず、レジストリや環境変数、エイリアスなどにも使用できます。

たとえば cd HKLM:\SYSTEM とするとレジストリ内に移動してレジストリを操作できるようになり、$pwd はこうした各種プロバイダ内部での位置を返す仕様なようです。

参考