PowerShell でホストプログラムとの通信を行う


自動変数は、色々ありすぎて個別に解説が必要になろうものですが、逆に言えば色々なところに情報があるので、設定変数と同様に1つだけを例として取り出すことにします。
今回取り扱うのは、$Host 自動変数です。スコープの話で少し触れましたが、PowerShell の実行環境は RunSpace という単位になっており、自作のアプリケーションでも RunSpace を生成してスクリプトを流し込むだけで PowerShell を内蔵したアプリケーションを作ることができます。*1 *2 RunSpace の内側で動いている PowerShell から RunSpace の外側にアクセスするためのインターフェースが $Host です。
ホストプログラムに依存した機能は、ホストプログラムが何であるかによって異なる動きをします。標準でついてくるホストプログラムである powershell.exe と PowerShell ISE を動かして次のようなスクリプトを実行してみましょう。ホストプログラムによって Write-Progress コマンドの結果が違うことを確認できます。

PS > &{
    foreach ($i in 0..9)
    {
        Write-Progress "My Action" "Running" -PercentComplete $($i * 10) -SecondsRemaining $(10 - $i);
        Sleep 1;
    }
    Write-Progress "My Action" "Done" -Completed
}

他にも、

PS > $in = Read-Host -Prompt "入力してください";

のようなホストを対象にしたコマンドを実行しても、実行結果が違うことがわかります。Read-Host や Write-Host, Write-Progress といったホスト依存のコマンドはホストプログラムに対する基本操作として PowerShell の RunSpace を生成するために準備することが必要となっている標準的な機能ですが、*3 ホストプログラムによってはホストプログラムに固有の機能を提供するものがあり、そこにアクセスするには $Host のメンバーである UI.RawUI と PrivateData を使用することになります。*4
たとえば、標準の PowerShell コマンドでは Windows のコンソール上で動作するので、コンソールのいくつかの機能を RawUI として公開しています。Get-Member コマンドを使ってどのようなものが公開されているか確認でき、たとえば WindowTitle を自由に変更することができるようになっていて、

PS > $Host.UI.RawUI.WindowTitle = "作業中..."

このような感じで、PowerShell の動いているコンソールウインドウのタイトルを書き換えることができるので、スクリプトの名前や処理状況を表示するなどで活用できます。また、PrivateData にはエラー出力や詳細出力といったアウトプット系の出力に対して PowerShell ホストがどのような色を使用するかを設定することができるようになっています。
ただ、あまり汎用的なスクリプトで $Host を直接使用することは避けたほうがよいでしょう。まだ、PowerShell のメジャーなホストプログラムはあまり数がありませんが、ホストプログラムが切り替わって動かなくなるのはあまり嬉しいことではありません。

*1:多くの場合は、アプリケーションに PowerShell をホストするよりも、アプリケーションにプログラマブルなインターフェースを設けてコマンドレット等を提供するほうが、ユーザが任意のホストを選択できるという利点が生まれて便利だったりします。

*2:PowerShell ISE では、ファイルメニューの「PowerShell タブの新規作成」を選ぶと、1つの PowerShell ISE 上に新しい RunSpace を作ることができ、図の MyApp.exe のような形になります。

*3:ホストプログラムが UI を持たない場合など、NotImplementedException や InvalidOperationException といったエラーを返すような場合もありうる。

*4:$Host.UI は基本的な UI 操作を提供しているインターフェースであり、Write-Host 等のコマンドはこの $Host.UI を使用して画面出力などを行っている。

クラシック コマンドと PowerShell の間のエンコード設定

設定変数についてだらだら書いても面白味がないので、従来からのクラシックなコンソール コマンドと PowerShell の間のやり取りについて書いてみます。
まぁ、普通にコマンドを使う分には大きな問題はないのですが、従来のシェルではコマンドの入出力は基本的にバイナリのストリームであり、ファイルでした。これは、お互いのコマンドが入出力をどのように扱うかを協力的に扱っていたということでもあります。
PowerShell ではコマンド間の入出力はオブジェクトのストリームになりました。これは、PowerShell とクラシックなコンソール コマンドの間ではオブジェクトとバイナリの間のマーシャリングが必要になるということです。また、PowerShell はコンソール コマンドとのデータのやりとりを文字列ベースで行おうと努力します。

PowerShell からクラシック コマンドへ

XML を扱うコンソール コマンド……たとえば構造的な差を取る XmlDiffPatch コマンドは XML を入力として受け付けるので、その XMLエンコードはドキュメント宣言に書かれた通りである必要があります。つまり、PowerShell のコマンドの出力であるオブジェクトを utf-8 のようなエンコードで出力しなければならないことになります。
オブジェクトがどのように文字列化されるか?については少し色々あるので、問題を簡単にするために string または string の配列で構成されたストリームであることにしましょう。
PowerShell からクラシック コマンドへ送信されるエンコードは設定変数 $OutputEncoding にて指定できます。デフォルトでは US-ASCII が設定されているため、日本語すら受け渡しがうまくいきませんので注意が必要です。

PS > "かきくけこ", "あいうえお" | sort.exe
?????
?????

PS > $OutputEncoding = [Text.Encoding]::Default
PS > "かきくけこ", "あいうえお" | sort.exe
あいうえお
かきくけこ

設定変数はセッション変数ですので、特定のエンコードに依存した処理を関数やスクリプトにすることで、呼び出し前のエンコードなんだっけ?みたいなことにならなくて済みます。
この例で使っている System.Text.Encoding.Default は、OS のログインユーザの既定の言語が取得できるようになっています。このあたりは、.NET Framework の知識や経験がある人ならば即断できることなのですが、PowerShell を使うぞ!と考えて調べものをしているかんじの人にはなかなか見つけられないのが、ちょっとつらいところですね。

クラシックコマンドから PowerShell

設定変数に $InputEncoding というのがあれば一発解決だったのですが、残念ながら $InputEncoding はありません。こちらも .NET Framework の知識が必要になるので中々調べるのが難しいのですが、System.Console.OutputEncoding を設定することで変更できます。PowerShell の入力になるものはコンソールの出力なので、こんなところに設定があるんですね。
System.Console.OutputEncoding のデフォルト値は Default になっていて、日本語を既定としている環境からなら最初から cp932(ShiftJIS) のデータが入力できます。これは、PowerShell を動かしているコンソールウィンドウでキーボードを叩いて入力している文字列もこの設定の影響を受けるからです。たとえば、この値を System.Text.Encoding.ASCII に設定すると IME を使って日本語を入力しても最初の例の出力のようにすべて "?" に置換されてしまうようになります。*1

System.Console.OutputEncoding を設定する場合、2つの注意点があります。
1つめの注意点としては、セッション変数ではないというところです。つまり、元の値を覚えておいたり、その値を再設定するのは PowerShell を使う側や、スクリプトの責任になります。
もう1つの注意点は、ほとんどの人が Windows のコンソール上で PowerShell を使用していると思います。実際に System.Console.OutputEncoding を ASCII に変更してみた人は気が付いたと思いますが、Windows のコンソールはこの処理を検出して自動的にコンソールウィンドウのコードページを追従させます。結果として、ASCII を設定すると見た目が ASCII コードページのフォントに変わり、IME も無効になって日本語の入力ができなくなります。

設定した System.Console.OutputEncoding を戻すには、trap や try-finally, begin-process-end を使うかんじになります。

# 現在のエンコードを保管しておく
$enc = [Console]::OutputEncoding;
try
{
    # コンソールの出力を utf-8 に変更する
    [Console]::OutputEncoding = [Text.Encoding]::UTF8;

    # 2つの XML の差分を取得する
    $xml = [xml] XmlDiffPatch source1.xml source2.xml;

    # 差分を出力
    foreach ($diff in Select-Xml $xml -XPath "//Diff")
    {
       ...
    }
}
finally
{
    # エンコードを元に戻す
    [Console]::OutputEncoding = $enc;
}

こんなかんじですね。

*1:入出力される文字が ? になるのは Encoding.EncoderFallback, Encoding.DecoderFallback に ? が設定されているため、ASCII の範囲にない文字が ? に置換されるためです。

PowerShell のセッション変数とスコープ

スコープという概念は、多くのプログラミング言語にあるので、親しみやすい概念ではあると思います。
いくつかの代表的なプログラミング言語とは違い、PowerShell では

  • 親スコープに作成したものと同じ名前を持つ変数を作成できます。
  • 関数やスクリプトを実行する際に、子スコープを作らないことができます。
  • 子スコープには見えない現在スコープにだけ有効な変数を作成できます。

PowerShell では現在のスコープをローカルスコープと呼びますが、0番スコープと呼ばれることもあります。親スコープは1番スコープと呼ばれることがあり、親の親は2番スコープになります。
PowerShell で最も外側のスコープは Runspace です。各 Runspace の持つスコープには global という名前がついているので、環境変数が env: というプレフィックスでアクセスできるように、このもっとも外側にあるスコープには global: というプレフィックスを指定して、Runspace 内の任意の場所からアクセスできるようになっています。
global 以外のスコープ指定プレフィックスとしては、script: があります。スクリプトスコープは、スクリプトを実行したときに作成されたローカルスコープを指します。

あと、変数変数と書いていますが、実際は関数もスコープ毎に定義されますので、スコープに関することは関数定義を行う際にも有効です。

同じ名前の変数が作れるということ

たとえば、

PS > function foo { write $a; $a = $a + 1; write $a; }

このような関数を作成して次のように実行すると、こんな結果になります。

PS > $a = 3
PS > $a
3

PS > foo
3
4

PS > $a
3
  • foo を実行すると、関数呼び出しによって新しいスコープが作成されます。
  • 最初の write $a; では、$a の内容を出力しますが、作成されたばかりのローカルスコープには $a という変数はありません。このため、親スコープの $a が表示されます。
  • 次の $a = $a + 1; では、同様に親スコープの $a の値を取り出し、1 を追加したあとでローカルスコープに $a という変数を新規作成します。
  • 最後の write $a; では、$a というローカルスコープの変数を表示しています。

foo が実行されても呼び出し元には影響がないため、実行後に $a を表示しても 3 が表示されるわけです。

スコープを作らないということ

先ほどの実行例に、少しだけ変更を行います。

PS > $a = 10
PS > $a
10

PS > . foo
10
11

PS > $a
11

foo を呼び出す際に、foo の前に . を付けただけです。これだけで foo を現在スコープで実行することができるようになります。なんか便利!ですよね?
自分のスコープにスクリプトや関数による影響を取り込むことは、ちょっとあぶなっかしいことですが、ちょっとしたスクリプトデバッグ用途にドット指定で呼び出して、実行後の変数の中身を見たりとか、自分で自分を書き換える関数とか、意外に便利なこともあります。

子スコープから見えない変数

変数の一般的ではない機能を使用するためには、$name 形式ではなく Set-Variable コマンドレットや Get-Variable, New-Variable コマンドレット等を使用する必要があります。たとえば、-scope オプションを使用すると親や先祖のスコープにある変数を直接操作することができます。

PS > $x = 1;
PS > Get-Variable "x" -Scope 0
1

PS > Get-Variable "x" -Scope 1
100

こんなかんじで、指定した番号のスコープの変数を取得できたりします。子スコープに見えない変数には、Private というオプションを指定します。

# 既に存在する $a を削除する
PS > Remove-Variable "a"

# 新しい変数 $a を Private オプション付きで作成
PS > New-Variable "a" 10 -Option Private
PS > $a
10

# foo からは $a が見えない
PS > foo
1

PS > $a
10

# スコープを作成しないので $a を参照し、書き換えることができる
PS > . foo
10
11

PS > $a
11

Private の他には、定数を作成する Constant や読み取り専用の変数を作成する ReadOnly といったオプションが指定できます。ドット指定の呼び出しとよく似た機能として、AllScope というオプションもあります。これは変数を子スコープに引き継ぐことができるというものです。

PS > Remove-Variable a
PS > New-Variable a 20 -Option AllScope
PS > $a
20

PS > foo
20
21

PS > $a
21

PS > . foo
21
22

PS > $a
22

実行結果を見てわかると思いますが、ドット指定の有無に限らず呼び出し先での変更の影響を受けています。

PowerShell の変数

PowerShell を使用されている方で変数を使用していない方は少ないと思いますが、PowerShell には変数として

の4種類があり、それぞれ微妙に異なる用途があります。

環境変数

環境変数は、Windows 等の OS の提供する変数で、多くの人に馴染みのある変数です。PowerShell では環境変数は Environment というプロバイダによって実装されていて、標準では env: というドライブレターでアクセスできるようになっていますが、通常は変数名の前置として使用します。

# 環境変数 HOMEDRIVE の値を表示
PS > $env:HOMEDRIVE
C:

# 環境変数 PATH の値を ";" で分割して表示
PS > $env:PATH -split ";"
C:\Windows\system32
C:\Windows
C:\Windows\System32\Wbem
C:\Windows\System32\WindowsPowerShell\v1.0
C:\Program Files\Consoles
C:\Program Files (x86)\Consoles

# 環境変数 PATH に C:\Program Files\Java\jdk1.7\bin を追加する
PS > $env:PATH += ";C:\Program Files\Java\jdk1.7\bin";

ドライブとして扱えることを利用して、環境変数を列挙したりすることもできます。たとえば、dir コマンドを使用して env: ドライブを参照すると、

PS > dir env:

Name                 Value
----                 -----
ComSpec              C:\Windows\system32\cmd.exe
HOMEDRIVE            C:
  :

と、環境変数の名前と設定値の一覧を得ることができます。この出力からもわかるように、環境変数は Name と Value のペアで作成されているので、

PS > dir env: | where { $_.Name -match "path") }

Name                 Value
----                 -----
CLASSPATH            ....
HOMEPATH             ....
Path                 ....
PATHEXT              ....
PSModulePath         ....

と、where コマンドを使って絞り込んだりすることもできます。

セッション変数

残りの3つの変数は、すべてセッション変数と呼ばれる変数です。これは、PowerShell のスコープ毎に作成される変数になります。環境変数と同じように Variable: ドライブとしてマップされていますが、PowerShell 自身が提供している機能であるため他のプロバイダとはやや違う動きをします。
各変数とスコープについては次回以降にして日付を稼ぐとして、軽く。

自動変数

自動変数は読み取り専用のセッション変数です。自動変数は PowerShell のコマンドの実行結果や各種状態を問い合わせる手段として利用できます。
たとえば $? という自動変数には、直前に実行されたコマンドが成功したかどうかが格納されていますし、$Error という自動変数にはコマンドの実行結果として発生したエラーが格納されています。
よく使う自動変数としては、$_, $True, $False, $NULL, $Matches あたりがあります。自動変数の一覧と解説を見たい場合には、help about_Automatic_Variables で確認することができます。*1

設定変数

設定変数は、PowerShell の動作を変更するための変数です。自動変数と非常に似ていますが、書き込み可能であるところが自動変数とは異なります。
たとえば $ErrorActionPreferece という設定変数は、コマンドからエラー出力が発生した場合に PowerShell が実行の継続をどのようにするかのデフォルト値を設定することができます。既定では Continue になっていて「エラーメッセージを表示して次に進む」という動作になっていますが、Inquire を指定すれば「エラーメッセージを表示して、次に進むかどうかをユーザに問い合わせる」になりますし、Stop を設定すれば「エラーメッセージを表示して停止する」という指定になります。
$ErrorActionPreference は、あくまでデフォルト値の設定です。ある特定のコマンドだけエラー処理を変更したい場合には、そのコマンドに対して -ErrorAction パラメータを指定することでこの変数の設定値を上書きすることができます。
設定変数の一覧と解説を見たい場合には、help about_preference_variables で確認することができます。*2

ユーザ変数

自動変数でも設定変数でもないセッション変数は、利用者が自由に作成できる変数になります。
変数名には、かなり柔軟な名前が使用できます。PowerShell の構文解釈上に問題があるような名前を使用する場合には、{ } を使って変数名の範囲を明示することができます。{ や } 自身を指定する場合には { } を使って変数名の範囲を明示した中でエスケープシーケンスを使用します。

# 変数名に記号等の文字を含めるためには { } が必要
PS > ${abc-def} = 1

# 変数名に { 自身を含めるにはエスケープ文字を使用する
PS > ${abc`{def`}ghi} = 2;

# 作成された変数を見る
PS > dir variable:

Name                 Value
----                 -----
abc-def              1
abc{def}ghi          2

# 名前の前半が同じ変数を作成する
PS > $a = 1
PS > $abc = 3

# 文字列による変数展開を行う場合、より長い名前に一致する
PS > "$ab $abc-def"
 3-def

# 変数名の範囲を明示することで、正しく変数名を認識させる
PS > "${a}b ${abc-def}"
1b 1

*1:ヘルプは部分一致検索されるので、初期状態の PowerShell であれば help about_au まで入力すれば about_Automatic_Variables だけがヒットします

*2:ヘルプは部分一致検索されるので、初期状態の PowerShell であれば help about_pre まで入力すれば about_preference_variables だけがヒットします

Roslyn

Microsoft Roslyn CTP
http://www.microsoft.com/download/en/details.aspx?id=27746

むかし、Subversion が世に出た時に CVS 等との違いとして様々な違いがあげられましたが、

  • Subversion は再利用可能なライブラリである

という項目がありました。Subversion はコンソール用のインタラクティブなプログラムを持っていましたが、あくまでライブラリとしての Subversion に対するフロントエンドの1例であったというものです。
このことは、Subversionリポジトリを操作するためのクライアントの多様性や、リポジトリそのものを直接操作してメンテナンスやミラーリングを行うようなツールの開発を容易かつ高品質に保っただけでなく、リビジョン管理システムとは関係がないアプリケーションでも、自身のデータ管理に Subversion を組み込むことを可能としました。

Roslyn は、(それだけではありませんが)これと同じことを C#VBコンパイラに対して実現しようというものです。
実際のところ、こちらの記事によると、C# 3.0 でクエリ式構文を追加する際にこのような試みは行われていて、Visual Studio 2008 はそのような仕組みをうまく使うようになっているそうです。この時の作業にはいくつもの課題があり、それを解決するためのプロジェクトが Roslyn であったとされています。*1

エンドユーザ向けに見える形での一番の期待は、Office シリーズの VBA に対して C#/VB が乗り込んでくれることかな?*2 ちょっとしたことをするだけならいいんですが、ちょっとしたことで済まないこと……特に Access は……なにかと VBA にひっぱられて苦労するものです。

Power Shell あたりはバックエンドの実装には影響があっても、フロントエンドの仕様にはあまり大きく影響しないと思いますが、makeshell コマンドあたりは大きくかわってコンパイラ相当にばけるかもしれませんね。

*1:C++ native から Managed な世界への変更や、Visual Studio 専用のインターフェースを公開できる形にしたり、C#VB で異なるインターフェースを統一したりする作業があげられています

*2:このことに、Roslyn は必須ではなかったと思いますが、大きく貢献するでしょう

15. Trac の初期設定(webから)

登録しておいた管理権限のあるユーザでブラウザを起動して Trac に接続すると、メインメニューに「管理」が表示されるので、そこから設定を変更することができます。
プロジェクト名やチケットのマイルストーンや重要度など、必要とお好みにあわせてどんどん変更しましょう。
プラグインの設定ページを開くと、デフォルトでいくつかのプラグインが無効になっていることに気が付くと思います。0.11 では Subversion のフックスクリプトとして提供されていたコミットコメントのチケットへの反映機能や、チケットに削除ボタンを追加するなどが無効になっていますので、必要な方は有効にするとよいでしょう。
うちの場合、post-commit だけじゃなく pre-commit を利用してチケットの属性と合致しないコミットを拒否したりするような設定もしていますので、それに対応したカスタム属性なんぞを足したりすることもあります。