◆Write-Hostを使ってみる言わずと知れたホストに出力するコマンドレット。簡単に使えて、何の変哲もないといえば無いのだが、実はなんとなく良く分からないコマンド。コンソールに色つきで出力したい、なんて時はこのコマンドの出番なのだが意外と色々戸惑うことも有ったので少し纏めておく。Bいままで特に不思議に思わず普通に使ってきたのだが、2つめのコマンドはなぜこんな結果になるのだろう。2番目に”B”と解釈されるものと思う。位置パレメータを1つしか持たない「Write-Host」はエラーになるのが普通ではなかろうか。実際、「Write-Warning」でためしてみると、予想通りのエラーになる。パラメータの型が<Object>で位置パラメータが1つ、と言ったあたりに何か理由があるのかとも思って調べてみたが結局良く判らなかった。Trace-Commandで見てみると、パラメータがArrayListとして解釈されているっぽい感じではあるが、それがなぜかまでは判らない。すべてのコマンドを検証したわけではないが、このような動作をするのは他に「Read-Host」くらいしか見つけられなかった。現時点ではそういうものだと思うことにしよう。(^^;次に、連想配列を「Write-Host」に渡した時の動作を見てみる。System.Collections.DictionaryEntryそのまま出力すれば内容が表示されるのだが、こいつに色を指定しましょうなどと思って「Write-Host」を使うと残念な結果になる。この時、「Write-Host」がどのような動作をしているのかを確認してみる。「DictionaryEntry」が3つ出力されているので中身をループしながらToStringって感じだろうか。%{$_.ToString()}System.Collections.DictionaryEntrySystem.Collections.DictionaryEntrySystem.Collections.DictionaryEntry内容的には合っているが表示形式がちょっと違う。System.Collections.DictionaryEntry実際にはこんな感じの条件がついているのかな。System.Collections.DictionaryEntryさて、実はここからが本題で、これまでの話とはそれほど関係ないのだが、以下の記事でハッシュテーブルに色をつけて表示するにはどうすればよいかという話が有った。hashtable全体に色をつければ良いようなのでOut-Stringして結果をWrite-Hostするだけの事なのだが通常は特定の行に色を付けたいよなと思って考え始めたところWrite-Hostの仕様がいまひとつ判らなかったという事で前段の調査になった。◆特定のプロセスに色をつけて表示するのだが、若干うまく行かないケースがあるので少しだけ違った方法でやってみた。今回も今ひとつスマートな方法は思いつかず、以下のような若干ベタな感じ。@para}やっぱり素直にOut-Stringでやったほうが良さそうかな。@para}本当はToStringをオーバーライドしたりするとスマートかなと思って書き始めたのだが、そちらはうまく行かなかった。基礎<アトミックゼロ幅アサーション><アトミックゼロ幅アサーション>なんか難しい言葉ですが、要はパターンがどのような場所で一致するかを表す指定のようです。複数行オプションが有効の場合は行の先頭。'(?m:^de)'True$文字列の末尾。複数行オプションが有効の場合は行の末尾。'(?m:bc$)'True\A文字列の先頭でマッチ。'(?m:\Ade)'False\Z文字列の末尾または末尾の改行の前でマッチ。'(?m:bc\Z)'False\z文字列の末尾でマッチ。'de\z'True\G先頭から最後まで指定したパターンの連続を表す指定のようです。マッチしない箇所が出てきたらそこで終わり。「Match」クラスなどを使って連続してパターンを検索するのに使用する。以下はMSDNに載っていたサンプルをPowerShellに変換したものです。$match.NextMatch()}0または1個のカンマで区切られたワード文字(間にスペースが1つ有っても良い)を検索するパターン。ループしてマッチするものをすべて見つける。途中でマッチしない箇所があるとそこで終了します。+\Gの指定がないときはマッチしない箇所は飛ばして最後まで継続します。\bワード境界でのマッチ。'\bhell'False\Bワード境界以外でのマッチ。◆関数の作り方?で悩んでいたのですが、色々と試行錯誤の末、なんとなく解決策らしき方法を見つけました。nameスクリプトブロックの「GetSteppablePipeline」メソッドで「SteppablePipeline」オブジェクトを作ってあげると、あたかもパイプライン中で使用しているかのように入力オブジェクトを続けて渡せるようになるようです。「Process」メソッドで1つずつ渡して行き、終わったら「End」メソッドを呼ぶ、といった感じのようです。手探りなので正解かどうかは判りませんが、とりあえずGridViewがウィルスのように湧いて出てくるのは回避できています。◆スクリプトブロックを使うを書いた時このメソッドの存在には気づいていたのですが、使い方が今ひとつ判っていませんでした。これで今年もすっきり終われる、と思ったのですがパラメータで指定した時の結果が、こんな感じになってしまいます。Out-GridViewのように渡した状態ですね。(ps)とやると同じ結果になるじゃないですか・・・。ん~、どういうことなんでしょう。バグ?またまた、色々と調べた挙句、よくよくヘルプを見ると、などときっぱり書いてある・・・。ん~、そりゃ実質パラメータ指定には対応していないってことじゃないですかい・・・。d:\documents\wk.txt}.GetSteppablePipeline()こんな感じで確認したところ、一応OKっぽい感じでした。なんとなく、今ひとつ報われない結果となりましたが、色々と助言を頂きながらパイプライン・関数・スクリプトブロックと少し理解が深まったような気がします。◆パイプライン中の処理かどうかを判定する(挫折)関数がパイプラインで使われているのか、どうでないのかを判定する。調べてみると「$MyInvocation.PipelineLength」あたりを使うのが一般的みたいだ。(ps)一見OKなのだが、これではうまく判定できないケースがある。testなどと指定すると、「PipelineLength」は1になってしまうのである。これは正しい結果なのだろうかとちょっと疑問にも思うが、なんにせよこれでは使えない。調べてみたが、他に方法もなさそう。ん~、パイプラインは難しい。牟田口さんからヒントを頂き$inputでの判定を検証しました。判定自体はよさそうです。$inputはBeginブロックで判定できないのが若干使いづらいかもしれません。(Firstブロックみたいな奴があると便利なのですが・・・)パイプライン入力が1つだけであれば、$PSBoundParametersでその存在を確認する方法で良いかもしれません。見落としていたのですが「$myinvocation.ExpectingInput」と言う奴が使えそうな感じにも見えます。◆$inputの使い方の内容でまだ悩んでいる。Out-GridViewこの部分を纏めたいというところから来ている。単純に関数にしてもダメだし、関数を入れ子にしても結局同じ問題にぶつかるし・・・。なんで、PowerShellとあろうものがこの程度のことができないのだろう、と暫く考えもしたのだが、そもそもやろうとしていることがPowerShellの思想にそぐわないのでは無いかと思えてきた。出力系のコマンド(Out-xxxxやFormat-xxxx)は独立して使うべきものなのではないかと。Out-GridViewこれはこのまま使うべきものという方針にしよう。◆$inputの使い方Webを眺めていたらこんな記事が目に止まった。Blog簡単に解釈すると、「Select-Object」するときに、プロパティをすべて表示させたいのだが一部のプロパティだけを先頭に持ってくるにはどうしたら良いかという話のようだ。全項目指定すれば順番に並べることは可能だがそれは面倒。*こんな指定が出来れば良いのだが、プロパティの重複でエラーになる。そこで以下のような関数を書いてみた。$proplist}}確かにキー項目のようなものだけ先頭にしたいときはあるなぁと思い、プロファイルに入れることにした。ogvこれを実行したらとんでもない目にあった。nameという訳で、纏めてGridVew出力に変更したのが以下のサンプルである。nameこれにて一件落着という所ではあるのだが、なんとなくすっきりしない。そもそも、このPROCESSブロックは必要なのだろうか。最初にやったように、単純にOut-GridViewにパイプするととんでもないことになるのは、このPROCESSブロックのせいである。Out-GridViewを使わないにしても入力パイプライン1件ずつに対してSelectするのは無駄ではないだろうか。出力にについても、本来以下のように1件ずつ出力されているのがマージされて結果的に纏まって表示されているに過ぎない。そこで、PROCESSブロックを使わずに$input自動変数で入力を纏めて取ってくるよう変更し比較してみようと思う。nameこんな感じで良いだろうと思ったのだが、これではうまくいかない。12行目の$inputは参照できるのだが、不思議なことに18行目では$inputが参照できないのである。(何も返ってこない)実は$inputは「ArrayListEnumeratorSimple」という型になっていて入力パイプラインのコレクションに対する「列挙」なのである。一度最後まで読みだすとそのままではもう一度読みだすことが出来ない。そこで、18行目の前に、$input.Reset()を追加してあげる必要があるのだ。最後に、実際両者の性能はどうなるのか比較してみた。name}結果を見ると、予想以上に$inputを使用したサンプルのほうが速いのが判る。基本的に私はPowerShellで速度を論じるのは好きではないが、これだけの速度差があると看過できない問題に思える。牟田口さんからコメントを頂きちょっと見直しました。(いつもありがとうございます)まさにご指摘の通り、本質的に時間がかかっている原因はプロパティの解析を毎回行なっているところにありますね。時間が違いすぎておかしいなとは思っていたのですが、安易にそのまま掲載してしまいました。(^^;ご指摘内容を踏まえて、当初の目的であるグリッドビュー出力のサンプルを再掲します。name}これで両者ほぼ速度的には似たようなものになりました。ターンアラウンドタイムは変わらないのですが、レスポンスタイムについては$inputを使ったほうが圧倒的に速いようです。これは、まさに牟田口さんご指摘のパイプラインの威力なのでしょう。ひとつご指摘いただいたパラメータを直接指定できなくなることにつきましては私も懸念しておりました。パラメータ指定の時とパイプライン入力の判定をして処理を振り分けないといけなくなるのでスマートじゃないんですよね。パイプライン入力を受け付けるにも関わらず、ValueFromPipelineなプロパティが存在しないぞとか思ったりして・・・。自己紹介minminnana地方でSE(プログラマ)をやって20年。気づけばいつの間にやら窓際人生。このまま余生を過ごすには若干心残りな2児と2匹(ウサギ、犬)の父。SEと言っても窓際なのでブログやWEBなどにはめっぽう疎い・・・。独学で.NET系を弄ってみるがCOBOLプログラマーにはだいぶ敷居が高い。