本記事は PowerShell Advent Calendar 2016 の 19 日目の記事です。
昨日は stknohg さんの CLR/H #103 ~ クリスマス オブ ザ デッド ~ でPowerShellをふりかえりましたです。
明日は牟田口さんの AST Visitorを使った静的解析(仮) です。
困った
先日スクリプトを書いていてハマったんですが、PSRemoting でリモート処理をしている時に、ローカルで定義した関数を使うことはできないんですね。
用語 'Show-Hoge' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラムの名前として認識されません。名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確認してから、再試行してください。
とはいえリモート処理の内容をすべて一つのスクリプトブロックに押し込めるのは頂けません。
やはり、長くなってくると分割したくなりますよね。
何とか使えるようにする手はないものでしょうか。
鍵は Function ドライブにあり
PowerShell には様々なドライブが存在します。ファイルやレジストリは言うまでもなく、環境変数を格納する Env ドライブや、PowerShell の変数を格納する Variable ドライブなど。
そんな中の一つに、関数を格納している Function ドライブというのがあります。
Get-ChildItem Function:\
と打つと、関数の一覧が表示されます。
実は、この Function ドライブに関数を定義してやることで、リモート環境でもスクリプト関数を使うことができるのです。
ローカル側であらかじめ ${function:Show-Hoge} という形で関数への参照を取得しておいて、それをリモート側で $using:func と using 付きで利用するのがポイントです。
しかしこれでは不完全
これで Show-Hoge 関数を送り込むことはできました。
しかし、Show-Hoge がさらに内部で別の関数を呼んでいたら? 送り込んだのは Show-Hoge だけですから、内部の関数は実行できずにエラーになります。
内部の関数も同様に送り込んでやればいいのですが、内部で呼ぶ関数が変わるたびに、送り込む部分も変更しなければなりません。
そこで、直接呼んでいる再外周の関数だけ指定してやれば、それらが内部で呼んでいる関数は自動的に含まれるようにしようということを思いつきます。
無駄に汎用的にしたがる。悪い癖です。
AST を使った関数の再帰的解析
というわけで、関数内で呼んでいる他の関数、その関数からまた呼ばれている他の関数…というように、再帰的に関数定義を引っ張ってくる関数を作りました。
PowerShell の構文解析には PowerShell 自身が持っている AST(Abstract Syntax Tree:抽象構文木)機能が使えます。
AST に関する詳しいことは、牟田口さんが書いてくださるらしいので、そちらに丸投げさせて頂くとして…
呼び出している関数を再帰的に取得する関数がこちらです。
ContainSelf を指定すると、-ScriptBlock で渡した関数自身を結果に含みます。
Recursive を指定すると、内部で呼んでいる関数を再帰的に取得します。
Context というハッシュテーブルに処理済みの関数を追加し、これを引き回すことで、重複処理を避けています。
この関数は Function ドライブ内のアイテムの形式(FunctionInfo)で返すので、使い方が先ほどの例とちょっと違っています。
こんな感じ。
これでリモート処理でも躊躇なく関数分割ができます。
おわり
このブログを書きながら検証していて気づいたのですが、Function ドライブにアイテムを作らなくても、Invoke-Expression でも行けました。
${function:Show-Hoge}.Ast.ToString() で関数全体の文字列が取得できるので、それをリモート側に持って行って Invoke-Expression するだけです。こっちの方が簡単かな。
余談ですが、最初は platyPS を使ったヘルプ作成のことを書こうと思っていました。
が、いまいち盛り上がらなかったのでこっちに差し替え。
platyPS については機を改めて書きたいと思います。
しかし、はてなブログはいつになったら PowerShell のシンタックス ハイライトに対応してくれるのでしょうか…