この記事は以下のシリーズの4番目の記事です。
この記事でやること
PowerShellでファイル操作を始めると、かなり早い段階で「パス指定」にぶつかります。
たとえば、次のような処理です。
Copy-Item -Path ".\sample.txt" -Destination ".\backup\sample.txt"
この .\sample.txt や .\backup\sample.txt が何を意味しているのかを理解していないと、手動では動くのに、タスクスケジューラでは動かない、ということが起きます。
この記事では、PowerShell初心者向けに、次の3つを整理します。
相対パス。
絶対パス。
$PSScriptRoot。
特に、.ps1 ファイルとして保存して実行する場合は、$PSScriptRoot を理解しておくとかなり安全になります。
検証用フォルダを作る
まず、検証用フォルダを作ります。
New-Item -ItemType Directory -Path "C:\work\path-test" -Force
Set-Location "C:\work\path-test"
確認します。
Get-Location
次に、検証用ファイルとフォルダを作ります。
Set-Content -Path ".\sample.txt" -Value "PowerShell path test"
New-Item -ItemType Directory -Path ".\backup" -Force
一覧を確認します。
Get-ChildItem
sample.txt と backup フォルダが見えれば準備完了です。
絶対パスとは
絶対パスとは、ドライブ名から始まる完全な場所指定です。
たとえば、次のような指定です。
C:\work\path-test\sample.txt
PowerShellで書くと、次のようになります。
Get-Content -Path "C:\work\path-test\sample.txt"
絶対パスのメリットは、どこから実行しても対象が明確なことです。
たとえば、今いる場所が C:\work\path-test でなくても、次のコマンドは動きます。
Get-Content -Path "C:\work\path-test\sample.txt"
一方で、絶対パスには弱点もあります。
フォルダ構成が変わると修正が必要になる。
別のPCに持っていくと動かないことがある。
ユーザーごとにパスが違う場合に扱いにくい。
そのため、実務では「固定された場所を明示したいとき」は絶対パスを使い、「スクリプトと同じ場所のファイルを使いたいとき」は後述する $PSScriptRoot を使うとよいです。
相対パスとは
相対パスとは、現在いるフォルダを基準にした場所指定です。
たとえば、現在の場所が次の場合を考えます。
C:\work\path-test
この状態で、次のように書けます。
Get-Content -Path ".\sample.txt"
この .\sample.txt は、「今いるフォルダの中にある sample.txt」という意味です。
現在の場所は、次のコマンドで確認できます。
Get-Location
.\backup\sample.txt と書けば、「今いるフォルダの中の backup フォルダにある sample.txt」という意味になります。
Copy-Item -Path ".\sample.txt" -Destination ".\backup\sample.txt" -Force
コピーできたか確認します。
Get-ChildItem ".\backup"
.\ の意味
PowerShellでよく見る .\ は、「現在のフォルダ」を意味します。
たとえば、次の2つは、現在の場所が C:\work\path-test の場合、ほぼ同じ意味になります。
Get-Content ".\sample.txt"
Get-Content "C:\work\path-test\sample.txt"
ただし、相対パスは「現在の場所」に依存します。
現在の場所を変えてみます。
Set-Location "C:\work"
この状態で、次を実行してみます。
Get-Content ".\sample.txt"
おそらくエラーになります。
理由は、今の PowerShell は C:\work を基準にして sample.txt を探しているからです。
sample.txt は C:\work\path-test にあるため、見つかりません。
元の場所に戻します。
Set-Location "C:\work\path-test"
手動では動くのに、タスクスケジューラでは動かない理由
PowerShell初心者がよく詰まるのが、次の現象です。
手動で実行すると動く。
タスクスケジューラで実行すると動かない。
原因の1つが、相対パスです。
たとえば、次のようなスクリプトを書いたとします。
Get-Content ".\sample.txt"
自分で C:\work\path-test に移動してから実行すれば動きます。
しかし、タスクスケジューラから実行した場合、現在の場所が C:\work\path-test とは限りません。
そのため、PowerShellが別の場所を基準にして sample.txt を探し、ファイルが見つからないことがあります。
これを避けるために使うのが $PSScriptRoot です。
$PSScriptRootとは
$PSScriptRoot は、実行中の .ps1 ファイルが置かれているフォルダを表す変数です。
たとえば、次の場所にスクリプトがあるとします。
C:\work\path-test\test.ps1
この test.ps1 の中で $PSScriptRoot を使うと、次の値になります。
C:\work\path-test
つまり、$PSScriptRoot を使うと、現在の作業フォルダではなく、スクリプト自身が置かれている場所を基準にできます。
これが非常に重要です。
$PSScriptRootを使った安全な書き方
まず、次の内容で test.ps1 を作ります。
$samplePath = Join-Path $PSScriptRoot "sample.txt"
Get-Content -Path $samplePath
この書き方では、test.ps1 と同じフォルダにある sample.txt を読み込みます。
Join-Path は、フォルダパスとファイル名を安全につなげるためのコマンドです。
次のように文字列で連結することもできます。
$samplePath = "$PSScriptRoot\sample.txt"
ただし、実務では Join-Path を使う方が無難です。
$samplePath = Join-Path $PSScriptRoot "sample.txt"
スクリプトを実行します。
.\test.ps1
この方法なら、どこから実行しても、test.ps1 と同じフォルダの sample.txt を見に行きます。
ログファイルにも$PSScriptRootを使う
ログファイルを .ps1 と同じフォルダに出したい場合も、$PSScriptRoot が便利です。
$logPath = Join-Path $PSScriptRoot "log.txt"
Add-Content -Path $logPath -Value "処理開始: $(Get-Date)"
Add-Content -Path $logPath -Value "処理終了: $(Get-Date)"
これで、スクリプトと同じフォルダに log.txt が作られます。
タスクスケジューラで実行しても、想定外の場所にログが出るリスクを減らせます。
入力フォルダ・出力フォルダを作る例
実務では、次のようなフォルダ構成にすることがあります。
C:\work\path-test
├─ main.ps1
├─ input
├─ output
└─ log
この場合、スクリプト内では次のように書けます。
$inputDir = Join-Path $PSScriptRoot "input"
$outputDir = Join-Path $PSScriptRoot "output"
$logDir = Join-Path $PSScriptRoot "log"
New-Item -ItemType Directory -Path $inputDir -Force
New-Item -ItemType Directory -Path $outputDir -Force
New-Item -ItemType Directory -Path $logDir -Force
日付つきログファイルを作るなら、次のようにします。
$today = Get-Date -Format "yyyyMMdd"
$logPath = Join-Path $logDir "log_$today.txt"
Add-Content -Path $logPath -Value "処理開始: $(Get-Date)"
このようにしておくと、スクリプトを別のフォルダに移動しても、関連フォルダをまとめて管理しやすくなります。
相対パス・絶対パス・$PSScriptRootの使い分け
実務では、次のように考えると分かりやすいです。
| 種類 | 例 | 向いている場面 |
|---|---|---|
| 絶対パス | C:\work\path-test\sample.txt |
場所が固定されているファイルを明示したい |
| 相対パス | .\sample.txt |
手動操作や一時的な検証 |
$PSScriptRoot |
Join-Path $PSScriptRoot "sample.txt" |
.ps1 と同じ場所の設定ファイル・ログ・入出力フォルダを扱う |
初心者向けに言うなら、次のように覚えるとよいです。
手で試すだけなら、相対パスでもよい。
場所が固定なら、絶対パスでもよい。
.ps1 として保存して運用するなら、$PSScriptRoot を使う。
よくあるミス
ミス1:今いる場所を確認せずに相対パスを使う
Get-Content ".\sample.txt"
この書き方は、現在の場所に sample.txt がある場合だけ動きます。
現在の場所は必ず確認します。
Get-Location
ミス2:コピー先フォルダが存在しない
Copy-Item ".\sample.txt" ".\backup\sample.txt"
backup フォルダが存在しないと失敗することがあります。
先に作っておくと安全です。
New-Item -ItemType Directory -Path ".\backup" -Force
Copy-Item ".\sample.txt" ".\backup\sample.txt" -Force
ミス3:タスクスケジューラで相対パスに頼る
タスクスケジューラでは、想定した場所で実行されるとは限りません。
次のような書き方は、動く場合もありますが、環境によって不安定です。
Add-Content ".\log.txt" "処理開始"
安定させるなら、次のように書きます。
$logPath = Join-Path $PSScriptRoot "log.txt"
Add-Content -Path $logPath -Value "処理開始"
ミス4:パスに空白があるのにクォーテーションを付けない
次のようなパスには空白が含まれています。
C:\work\test folder\sample.txt
この場合は、必ずダブルクォーテーションで囲みます。
Get-Content "C:\work\test folder\sample.txt"
PowerShellでは、パスは基本的にダブルクォーテーションで囲む癖をつけると安全です。
まとめ
PowerShellでファイル操作をするなら、パス指定の理解は必須です。
特に重要なのは、次の3つです。
| 用語 | 意味 |
|---|---|
| 絶対パス |
C:\work\sample.txt のように場所を完全に指定する |
| 相対パス |
.\sample.txt のように現在の場所を基準に指定する |
$PSScriptRoot |
実行中の .ps1 が置かれているフォルダを表す |
初心者のうちは、次の方針で十分です。
PowerShell画面で試すだけなら、相対パスを使う。
固定の場所を明示したいなら、絶対パスを使う。
.ps1 ファイルとして保存して使うなら、$PSScriptRoot を使う。
特にタスクスケジューラで実行する予定があるスクリプトでは、相対パスだけに頼らない方が安全です。
最後に、実務でよく使う基本形を載せておきます。
$baseDir = $PSScriptRoot
$inputDir = Join-Path $baseDir "input"
$outputDir = Join-Path $baseDir "output"
$logDir = Join-Path $baseDir "log"
New-Item -ItemType Directory -Path $inputDir -Force
New-Item -ItemType Directory -Path $outputDir -Force
New-Item -ItemType Directory -Path $logDir -Force
$today = Get-Date -Format "yyyyMMdd"
$logPath = Join-Path $logDir "log_$today.txt"
Add-Content -Path $logPath -Value "処理開始: $(Get-Date)"
この形を覚えておくと、設定ファイル、入力フォルダ、出力フォルダ、ログファイルをスクリプトと一緒に管理しやすくなります。