記事検索

検索ワードを入力してください。
Sky Tech Blog
PowerShell スクリプトモジュールから、​参照元の​スクリプトファイルの​名前を​取得するのに​苦労した​話

PowerShell スクリプトモジュールから、​参照元の​スクリプトファイルの​名前を​取得するのに​苦労した​話

PowerShellスクリプトモジュール(.psm1ファイル)から参照元のスクリプト(.ps1ファイル)の名前を取得する方法について説明しています。具体的には、Get-PSCallStackコマンドとコマンドライン情報を利用する2つの方法を紹介しています。

PowerShell スクリプトモジュール(.psm1 ファイル)から 参照元の PowerShell スクリプト(.ps1 ファイル)の名前を取得するのに苦労したというお話です。

苦労したところは置いておいて、結論を知りたいという方は最後の【まとめ】をご覧ください。

背景

PowerShell で複数のスクリプト(.ps1)を連動させて実現する仕組みを 構築する機会があり、役割や機能ごとに.ps1を作成していました。

その際、例えば、デバッグログを出力するためのクラス・関数等、 どの.ps1でも同じことを行う処理も当然あり そういったものは、各.ps1から共通で使えるように スクリプトモジュール(.psm1)としてライブラリ化する という方法をとることもあるかと思います。

そうなったときに「参照元の.ps1ごとにデバッグログを分けてファイル出力したい」等、 参照元の.ps1のファイル名を取得したい、というケースが出てきます。

参照元の.ps1ファイルから、共通モジュールを呼び出す際に自分のファイル名を渡すことでも もちろん実現はできるのですが、あまりスマートではないな、と思い 参照元の処理に依らずに実現できないか、と調べて(試して)みました。

検証内容

今流行りの生成AIも活用しつつ方法を模索したのですが 提示された方法を試しても「参照元の処理に依らずに実現する」という目的に合致しないものも多く、 なかなかこれ、という解が得られません。 何度も押し問答を行った結果、たどり着いた方法が下記の2つです。

  • Get-PSCallStack の結果から取得する
  • .ps1 実行時のコマンドラインの情報から取得する

それぞれの詳細は下記の通りです。

Get-PSCallStack の​結果から​取得する

下記内容の、スクリプトおよび、スクリプトモジュールを用意します。 ※ExecutionPolicy の関係でバッチファイル経由で実行しています。

run1.bat

@echo off
cd %~dp0
powershell -NoProfile -ExecutionPolicy RemoteSigned .\script1-1.ps1

script1-1.ps1

# モジュール読み込み
using module "./module1.psm1"

module1.psm1

# コールスタック表示
(Get-PSCallStack | Format-Table Command, Position) |  Out-File output.txt -Encoding Default

run.bat を実行すると output.txt へ下記が出力されます。

output.txt

Command        Position                                                                                   
-------        --------                                                                                   
module1.psm1   (Get-PSCallStack | Format-Table Command, Position) |  Out-File output.txt -Encoding Default
<ScriptBlock> .\script1-1.ps1                             

最後の行に「script1-1.ps1」が見えますね! (Get-PSCallStack)[-1].Position.Text から取得できそうです。

.psm1 を手直しして

module1.psm1

# .ps1ファイル名取得
$ps1file = (Get-PSCallStack)[-1].Position.Text
# ファイルに出力して確認
$ps1file | Out-File output.txt -Encoding Default

run1.bat を実行すると output.txt へ下記が出力されます。

output.txt

.\script1-1.ps1         

取れました。

しめしめと、これでコーディングを進めていたのですが、 別のスクリプトで同じようにモジュールを組み込むと なぜか.ps1ファイルの名前が取得できていないことがありました。 よくよく確認すると、Get-PSCallStack の結果が下記のようになっています。

output.txt

Command      Position                                                                                   
-------      --------                                                                                   
module1.psm1 (Get-PSCallStack | Format-Table Command, Position) |  Out-File output.txt -Encoding Default

肝心の最後の行が出力されなくなっています。

この結果の違いは何だろう、と試行錯誤したところ、.ps1 の呼び出し方に違いがあることに気づきました。

.ps1 のファイル名が取得できる場合は、バッチファイルから下記のように呼び出しています。

run1.bat(抜粋)

powershell -NoProfile -ExecutionPolicy RemoteSigned .\script1-1.ps1

コールスタックの最後の行が出力されないスクリプトは、別の.ps1から下記のように呼び出していました。

script1-2.ps1(抜粋)

$proc = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile -File .\script1-3.ps1" -WorkingDirectory (Get-Location) -PassThru -Wait

.bat と.ps1なのでそもそもコーディングが違いますが、呼び方の違いがお判りでしょうか…? ※ .ps1から呼び出したらダメ、ということではありません。

・   ・   ・   ・

正解は…

-ArgumentList "-NoProfile -File .\script1-3.ps1"
                   ↑↑↑ここの部分

コールスタックの最後の行が出力されないときには、powershell.exe のパラメータに「-File」オプションを指定しています。 冒頭の例に挙げた run1.bat では -File は省略して、実行するスクリプトファイル名を記述していました。 試しに、バッチファイルでの呼び出しにも -File を付けると同じようにコールスタックの最後の行が出力されなくなりました。 とても謎な挙動です…。

目的である「参照元の処理に依らずに実現する」に対して若干思惑通りにはなりませんでしたが もしこの方法を試される場合はご注意くださいませ。

.ps1 実行時の​コマンドラインの​情報から​取得する

もう一つの実現方法です。 こちらの方がコーディングの量は増えますが、直感的にはわかりやすいかと思います。

下記内容の、スクリプトおよび、スクリプトモジュールを用意します。 ※ExecutionPolicy の関係でバッチファイル経由で実行しています。

run2.bat

@echo off
cd %~dp0
powershell -NoProfile -ExecutionPolicy RemoteSigned .\script2.ps1

script2.ps1

# モジュール読み込み
using module "./module2.psm1"

module2.psm1

# コマンドライン表示
[System.Environment]::CommandLine | Out-File output.txt -Encoding Default

run2.bat を実行すると output.txt へ下記が出力されます。

output.txt

powershell  -NoProfile -ExecutionPolicy RemoteSigned .\script2.ps1

.ps1 のファイル名を含んでいるので、あとはこれを分解すればよさそうです。

.psm1 を手直しして

module.psm1

$ps1file = ""
# コマンドラインを取得・半角スペースで分割
$cmdline = [System.Environment]::CommandLine -Split " "
# 分割した要素から、".ps1"を含むものを探す
foreach ($elm in $cmdline) {
    $elm = $elm -replace '"', ''
    if ($elm -match "\.ps1$") {
        $ps1file = $elm
        break
    }
}
# ファイルに出力して確認
$ps1file | Out-File output.txt -Encoding Default

run2.bat を実行すると output.txt へ下記が出力されます。

output.txt

.\script2.ps1                                                                               

取れました。

まとめ

まとめると実現方法としては下記2点。 絶対パスで指定されている場合も考慮して、最後の「\」より後だけを切り取る処理も入れています。

Get-PSCallStack の結果から取得する

# .ps1ファイル名取得
$ps1file = ((Get-PSCallStack)[-1].Position.Text -Split "\\")[-1]
# ファイルに出力して確認
$ps1file | Out-File output.txt -Encoding Default
※スクリプト実行時、powershell.exe のパラメータの「-File」は省略する必要がある

.ps1 実行時のコマンドラインの情報から取得する

$ps1file = ""
# コマンドラインを取得・半角スペースで分割
$cmdline = [System.Environment]::CommandLine -Split " "
# 分割した要素から、".ps1"で終わるものを探す
foreach ($elm in $cmdline) {
    $elm = $elm -replace '"', ''
    if ($elm -match "\.ps1$") {
        $ps1file = ($elm -Split "\\")[-1]
        break
    }
}
# ファイルに出力して確認
$ps1file | Out-File output.txt -Encoding Default

以上、何かしらの参考になれば幸いです。


\シェアをお願いします!/
  • X
  • Facebook
  • LINE
キャリア採用募集中!

入社後にスキルアップを目指す若手の方も、ご自身の経験を幅広いフィールドで生かしたいベテランの方も、お一人おひとりの経験に応じたキャリア採用を行っています。

Sky株式会社のソフトウェア開発や製品、採用に関するお問い合わせについては、下記のリンクをご確認ください。
お問い合わせ
ホーム