コマンドレットのパラメータを列挙してみる。2014の24日目の記事です。位置情報プラットフォーム」をPowerShellから呼び出して、位置情報(緯度、経度)を取得してみよう、という話になります。位置情報プラットフォーム」という機能が追加され、アプリケーションから現在位置情報(緯度、経度など)をAPIで取得できるようになっています。位置情報プラットフォームでは、位置情報をGPSがあればGPSから、なければWi-Fiの位置情報あるいはIPアドレスなどから推定して取得します。GPSがない場合でも位置情報を取得できる、いわば仮想GPSの機能がデフォルトで備わっているのがミソです。センサー&ロケーションプラットフォーム」というのがありましたが、OSデフォルト機能としては仮想GPSはありませんでした。Windowsというサードパーティー製アプリを追加すると仮想GPS使えたんですけどもね。位置情報プラットフォームをPowerShellで使うには、.NET4.0以上に含まれている、System.Device.Location名前空間配下に含まれるクラスの機能を用います。アセンブリ名としてはSystem.Deviceとなります。以下のような関数Get-GeoCoordinateを定義します。System.Deviceを実行して必要なアセンブリをロードする必要があります。関数本体ではまず、System.Device.Location.GeoCoordinateWatcherオブジェクトを生成します。位置情報プラットフォームにアクセスして、位置情報の変化を監視します。位置情報の変化を感知すると、PositionChangedイベントが発生し、取得した位置情報を、イベントハンドラの引数にGeoPositionChangedEventArgs<T>オブジェクトとして返します。PowerShellでは、.NETクラスのイベントを取得するには、Register-ObjectEventコマンドレットを用い、イベントを「購読」します。{処理内容}のようにして、イベントハンドラを記述するのが一般的です。今回は位置情報の変化の最初の一回(つまり、初期値の取得)さえPositionChangedイベントを捕まえればOKなので、-Actionは使用しません。代わりにWait-Eventコマンドレットを用い、初回のイベント発生を待機するようにしています。Wait-Eventコマンドレットは、当該イベントを示すPSEventArgsオブジェクトを出力します。PSEventArgsオブジェクトのSourceEventArgsプロパティには、当該イベントのイベントハンドラ引数の値(ここではGeoPositionChangedEventArgs<T>オブジェクト)が格納されているので、あとはそこから.Position.Locationと辿ることで、位置情報を格納したGeoCoordinateオブジェクトが取得できます。(注:あとで知ったんですけど、GeoCoordinateWatcherクラスには、同期的に位置情報を取得するTryStartメソッドというのがあって、これを使えばイベント購読は実は不要でした...まぁいっか)なお、関数のパラメータとしてLatitude(緯度)、Longitude(経度)を指定すると、現在位置ではなく、指定の位置を格納したGeoCoordinateオブジェクトを生成するようにしています。位置情報プラットフォームを有効にする」にチェックを入れておきます。あとはGet-GeoCoordinateをそのまま実行するだけです。Falseこのように現在位置が表示されるかと思います。いっても、緯度、経度が表示されたところでちゃんと取得できてるのかよく分からないので、以下のような簡単な関数(フィルタ)を定義しておきます。"http://maps.google.com/maps?q=$($_.Latitude),$($_.Longitude)"}このフィルタを使うと、指定の緯度経度周辺の地図を、標準のWebブラウザで開いたGoogleマップ上に表示してくれます。位置測定に用いたソースによってはkmオーダーでズレると思いますが、それでも何となく、自分がいる場所が表示されるのではないかと思います。先ほども書いたように、パラメータで任意の緯度、経度を指定することも可能です。位置情報プラットフォーム」を使って現在位置が取れるよ、という話でした。PowerShellでSystem.Device.Locationとかを使っているサンプルを見かけないので、何かの参考になれば幸いです。あとPowerShellでのイベントの扱い方についても復習になるかと。APIを呼び出して活用しよう、というようなネタを書くつもりだったんですが、長くなったんでまたの機会としましょう。2014の22日目の記事です。前回はMMLをパースし、音楽をBeepで再生するところまで作りました。MMLはテキストデータなので、テキストエディタで入力しても良いのですが、どうせなら、PowerShellでエディタも作ってしまいましょう。コードいきなりですが、エディタの本体コードを。$mml}使い方PowerShellコンソール上(ISEは不可)で、前回公開した、ConvertFrom-MML、Invoke-Beep、Invoke-Mmlの3つの関数をまず読み込み、続いて上記のEnter-MusicEditor関数を読み込みます。この状態でコンソール上でEnter-MusicEditorを実行すると、MMLエディタモードに入ります。MMLエディタモードでは、PCのキーボードを鍵盤代わりにして入力できます。Cキーを押下すると、「ド」の音がBeepで鳴り、コンソールに"C4"と出力されます。同様にFキー押下で「ド♯」が鳴り"C+4"と出力されます。どのキーがどの音に対応しているかは、上記コードの$keyMap変数に格納された連想配列を参照してください。(なんとなくですけど、楽器の鍵盤の配置と合わせてあります)Backspaceキーを押下すると、直前の入力を削除できます。EnterキーかEscapeキーを押下すると、エディタモードを終了します。出力したMMLは、コンソール上に表示が残るので、あとはコピーしてInvoke-Mml関数に渡してやると演奏することができます。$mml=Enter-MusicEditorとしてやると、入力したMMLをそのまま変数に格納できます。$mmlと実行すれば演奏できます。技術的には全然大したことをしてないですが、一応解説。[System.Console]::ReadKey()で入力したキーコードを判別します。引数に$trueを指定すると、入力したキー名をそのままコンソールに出力するのを抑制できます。ReadKeyメソッドはConsoleKeyInfoオブジェクトを返します。アルファベットキーについてはKeyCharプロパティの値、特殊キーについてはConsoleKey列挙体を返すKeyプロパティの値で調べることができます。あとは入力キーから対応する音名を連想配列$keyMapから取ってきて、その音名を[System.Console]::Write()でコンソール出力すると同時に、Invoke-Mml関数でその音をBeepとして鳴らしているだけです。いや、やはり無理があるか...。他のMMLコンパイラと併用して、ちょっと演奏しながらMML入力したいな、というときにもしかすると役に立つかもしれません。機能が少なすぎるんで、せめて↑で半音上げ↓で半音下げ、→で伸ばす←で短く、くらいはそのうち実装してみたいですね。MML作成にはあんまり役に立たないかもしれませんが、PowerShellでコンソールの入出力を制御するのは、こういう単純なものなら意外と簡単にできるという、サンプルにはなるかと思います。2014の11日目の記事です。Languageの略で、その名の通り音楽演奏データを記述するための言語です。BASICのPLAY文で使うあれです。MIDIファイルに変換するコンパイラもありましたよね。「ねこふんじゃった」の最初の部分をMMLで書くとこんな感じです。MMLはいろんな記法があるのですが、よくあるのはこういうのです。e-d-<g-4>g-rg-4e-d-<g-4>g-rg-4e-d-<g-4>g-4<e-4>g-4<d-4>frf4e-d-<d-4>frf4e-d-<d-4>frf4e-d-<d-4>f4<e-4>f4<g-4>g-rg-4ドの音をc、レの音をd、のように以下シの音をbまでアルファベット音階で割り当てます。半音上げる(シャープ)場合は音名のあとに+、半音下げる(フラット)場合は-を付与します。音の長さは4分音符なら4、8分音符なら8と書きます。付点は数字のあとに.を付けます。数字省略時は8分音符と解釈するのが普通のようです。オクターブを上げる場合は>、下げる場合は<と書きます(コンパイラによっては逆の解釈をする)。本物のMMLでは、音色の指定だとかトラックの割り当てだとか、もっとたくさん命令があるんですが、基本はだいたいこんなもんです。PowerShellで音を鳴らすにはPowerShellで音を鳴らすには、.NETのSystem.Console.Beepメソッドを使うのが一番手っ取り早いです。比較的古いWindowsではハードウェアのビープスピーカーから、比較的新しいWindowsではサウンドデバイス上で鳴ります。今回のMMLシーケンサーも結局はBeepメソッドです。ぎたぱそ先生がずっと以前に書いておられます。今回のはまあ、MMLシーケンサー部分を除けば二番煎じなんですけどもね。PlayerをCOM経由で実行する方法や.NET(WPF)のSystem.Windows.Media.MediaPlayerクラスを使う方法などがあります。音程の決め方Beepメソッドでは第1引数にビープ音の周波数をHzで、第2引数に再生時間をミリ秒で指定します。音程(音の高さ)はHzで指定する必要があります。ここでドの音は何ヘルツで、みたいな対応表をどこかから探してきてそのデータを使ってもいいんですが、今回はせっかくなので、PowerShellで計算して求めてみます。ある音程の一つ上のオクターブの音程の間の周波数比は、1:2です。ピアノ等の鍵盤楽器が近くにあれば、それを見てもらえば分かると思いますが、1オクターブには12の音程が含まれています。1オクターブの周波数比を12個均等に分割します(この周波数の決め方を平均律といいます)。隣り合う音程の周波数比は、1:12√2となるわけです。音程の周波数比はこれでわかりました。あとは基準となる音の周波数が分かれば、全ての音の周波数が計算できるわけです。時代や地域、演奏する楽器等によって微妙に違ってきますが、現代日本においては普通は、A(ラ)=440Hzが基準周波数です。あとは単純にかけ算して周波数を求めて連想配列に入れておくだけです。コードにすればこんな感じですね。1/12)をかけ、-で半音下がる場合は割ればいいだけです。音価というのは音の長さです。Beepメソッドの第2引数にはミリ秒で実際の時間を指定する必要があるので、これも計算で求めてやります。4分音符というのは、1小節(全音符)を4分割、つまり1/4した音価を持ちます。同様に8分音符は1/8ですね。付点がつくと音価は1.5倍になります。4分音符の具体的な音価はどうやって定めるのでしょうか。それを決めるのがテンポと拍子です。Minuteともいう)で定義されます。今回作るシーケンサーは4/4拍子、すなわち1拍=4分音符とする4拍子固定とするので、1分間に4分音符がBPM回含まれることになります。具体的なコードは...略します。単なるかけ算と割り算だけなんで。MMLのパースえっと時間がなくなってきたので、駆け足で説明します。MMLのパースとは、要するに最初の例のようなMML(テキストデータ)から、実際に演奏する音程と音価の組み合わせのデータとして組み立てる作業になります。今回のシーケンサーでは、繰り返し記号等はサポートしないので、単にMMLを一文字ずつ読み込んで、音名(cなど)を見つけたら、その後の文字を読んで、+があれば半音上げるなどして周波数を求め、その後に数字があればn分音符とみなし、先ほどの計算式から実際の音価を求めてやる。いうのを全部の音でくり返すだけです。この処理をやっているのがConvertFrom-MMLという関数です。MMLテキストを入力してやると、周波数(Frequency)や発音時間(Duration)などをプロパティとして含んだpscustomobject(Noteオブジェクト)を出力します。(Noteとは音符のことです)MMLの演奏パースしたMMLを実際に演奏するのがInvoke-Beep関数です。入力は、ConvertFrom-MMLで生成したNoteオブジェクトです。$note.Duration)するだけです(えー)。あ、あと休符の時はDuration分だけSleepを入れてやっています。MMLの>と<の意味を逆転する-Reverseスイッチ、BPMを指定する-Bpmパラメータ(デフォルト120)なんかも用意してます。実際にはMMLから直接演奏できるように、Invoke-Mmlというラッパー関数を用意しています。冒頭のねこふんじゃったのMMLを再生するにはこんな感じにします。@PSBoundParameters|Invoke-Beep}おわりに今回はMMLをパースしてBeepを演奏するスクリプトをPowerShellで作ってみました。これをシーケンサーというのはおこがましいと思いますが、まあたまにはこういう柔らかいネタもいいんじゃないでしょうか。ドレミの音がどうやって決められてるのかというのも、もしかして話のネタくらいにはなるかも。これ、シーケンサーといいつつ、入力機能がないですよね。それについては次回やります。PowerShellアドベントカレンダー2014はまだまだ残席ございます。あなたのPowerShell話を聞かせて下さい。2013の5日目の記事です。PowerShellにはJobが完了するまで待機するWait-Jobコマンドレットというのがあります。これはその名の通り、パイプラインから入力したJobオブジェクトがすべて(あるいはどれか一つが)完了状態になるまでスクリプトの実行を待機する効果があります。当然ながらWait-JobはJobオブジェクトにしか利用できませんが、任意の入力オブジェクトに対して待機条件を指定してやれば、その条件を満たすまで実行を停止するコマンドがあると便利なんじゃないかな?と常々思っていたので書いてみました。[<CommonParameters>]パラメータ-InputObject:入力オブジェクト。-Property:変更を確認するプロパティ名。-Value:-Propertyで指定のプロパティ値が、このパラメータに指定する値になるまで待機する。-FilterScript:プロパティを指定する代わりに待機条件をスクリプトブロックで指定する。-Any:入力のどれか一つが条件を満たすまで待機するようにする。(省略時は入力が全部条件を満たすまで待機)-PassThru:入力オブジェクトが待機条件を満たした時点で、そのオブジェクトを出力する。-IgnoreImmutable:最初から条件を満たしている場合は出力しない。-PassThruと併用。-AllOutput:タイムアウトした場合や-Any指定時に一部のオブジェクトしか出力していない場合でも、最終的に未出力のすべてのオブジェクトを出力してから終了する。-PassThruと併用。-IntervalSec:プロパティ値のチェック、もしくは待機条件スクリプトの実行の間隔秒数を指定。-TimeoutSec:最大待機秒数。この時間を過ぎると条件を満たしていなくても待機を終了する。停止しているサービスがすべて開始するまで待機する。上記と同じだが、開始したサービスを逐次表示する。停止しているサービスが少なくとも1つ開始するまで待機する。プロセスのワーキングセットが100MBを超えた段階で逐次表示する。上記と同じだが、最初から100MBを超えてるものは出力しない。ディレクトリ内のファイル容量がすべて50KBを超えるまで待機し、出力のFileInfo配列を変数に代入。3600問題点プロパティ値を取得するときにリアルタイムに値が反映されないオブジェクト(要するにGetした時点のプロパティ値がずっと固定されてるもの)に対しては正しく動作しません。いうか、PowerShellで扱うオブジェクトはほとんどそうなんじゃないかと思います(汗ServiceControllerオブジェクト、Processオブジェクト、FileInfoオブジェクト、DirectoryInfoオブジェクトについては、Refreshメソッドを実行すると、プロパティ値を現在の値に更新してくれるので、それを利用してプロパティ値を監視できるようにはしています。それ以外についても監視できるようにするには、たぶんそれぞれのオブジェクトに応じた監視方法を地道に調査して実装していくしかないんじゃないかなあと思います。INotifyPropertyChangedインターフェースを実装したクラスについては、PropertyChangedイベントをSubscribeしてプロパティ値の変更を追跡できるようにしてみようとちょっと思ったんですが、PowerShellで扱うオブジェクトにINotifyPropertyChangedを実装したクラスのものってそんなにあるんだろうか?と疑問を覚えたのでやめました。WMIオブジェクトについては何か共通の方法でプロパティ値変更を監視できないかなあと思ったんですが、結局IntervalSec間隔でクエリを発行する方法になってしまい、低コストで行う方法がちょっと思いつきませんでした。“ほげ”}みたいなコードを書いてゴリ押しすることもできるかと思います。感想というわけで、なんだか微妙な成果になって恐縮ですが、なんで無いんだろうと思っていた関数を実際に書いてみると、無い理由が分かったりするものなんだなあ、と思ったりした次第です。スクリプトの解説を何もしてないですが、あえて解説する程のものでもないこともないですが、まあ長くなるのでやめときます。入力オブジェクトを一旦全部取得してから、後続パイプラインに流し込む例としていくらか参考になるかもしれません。スクリプトブロックを二重にして$_に対象オブジェクトがきちんと格納されるようにする方法、若干トリッキーな気もしますが正式にはどう書くのが良いのか不明なのでこうしてみました。ポケットリファレンス」が発売となります。2012に対応して改訂新版として装いも新たに再登場となります。モジュール」の4部構成となっております。1.0/2.0/3.0全対応で、2.0以上で対応の機能、3.0で対応の機能にはそれぞれ分かりやすくマークを入れてあります。今回もいくつかサンプルスクリプトを掲載しており、それはサポートページでダウンロードできるようにする予定です。392ページだった初版から大幅増ページで592ページとなり、ポケットリファレンスといいつつ本当にポケットに入るのか?というボリュームとなっていますが、きっと皆様のPowerShellライフのお役に立てるものと思います。書店等で見かけた際は是非、お手に取ってご覧になっていただきたいと思います。2012、最終日の記事です。前回はAdd-Typeコマンドレットを使って独自のクラスを作成し、そのクラスを入力あるいは出力型に取る関数をどのように記述すれば良いのか、というお話でした。今回は前回に残した課題である、ユーザー定義の独自型の出力にちゃんとした書式を設定する方法について説明していきます。型データと書式設定データPowerShellは.NETオブジェクト(に限らず、型アダプタが存在するCOMやXMLなども、ですが...)をラッピングした型システムを有しているわけですが、このラッピング時にオブジェクトに対してPowerShell独自のデータを付与することができます。それが型データと書式設定データと呼ばれるものです。型データはクラスにPowerShellエンジンが付加する独自のメンバ(プロパティ、メソッド)です。ScriptMethod(スクリプトで記述されたメソッド)があります。.types.ps1xmlファイルにその定義を記述し、モジュールならモジュールマニフェスト(.psd1)のTypesToProcessプロパティに型データファイルパスを指定することで、インポート時に型データを反映させることができます。Update-TypeDataコマンドレットで後から型データファイルを読み込んで反映することもできます。3.0ではUpdate-TypeDataコマンドレットで.types.ps1xmlファイルを読むのではなく、直接任意のメンバを任意の型に追加することも可能になっています。Extendedとすることで$objに追加されたメンバがどれなのかが分かります。(ちなみに型アダプタによって追加されたメンバはAdapted指定で分かります)今回の記事では型データについてはこれくらいにして(またいつか改めて取り上げたいですが)、以下、本題の書式設定データの話をしていきます。書式設定データとは書式設定データも型データと同様、クラスに付加するデータなのですが、これはオブジェクトを出力する際のデフォルトの表示フォーマットを定義するものとなります。AdminService...のようにProcessオブジェクトが表形式で表示されます。ここで表に含まれるプロパティ、IdとProcessNameは.NETのProcessクラスが持つオリジナルのプロパティで、HandlesはHandleCountプロパティのAliasPropertyです。NPMやWSなども対応するAliasPropertyやScriptPropertyが定義されているのですが、たとえばNPMというAliasPropertyはあってもNPM(K)というメンバはありません。これを定義しているのが書式設定データになるわけです。そもそもこの表に含まれるプロパティの種類であるとか、もっというとProcessオブジェクトは特に指定がない場合は表形式で表示する、といった定義も書式設定データに含まれます。書式設定データも型データと同様にXMLファイルに定義されるのですが、その拡張子は.format.ps1xmlです。モジュールならマニフェストのFormatsToProcessプロパティに.format.ps1xmlファイルのパスを指定することで表示に反映されますし、Update-FormatDataコマンドレットによって後から反映させることも可能です。この書式設定ファイルはユーザー定義型に関しても定義を記述できます。ユーザー定義型に対応する.format.ps1xmlファイルを記述し、それを読み込むことで、自分がAdd-Typeで作った型に対しても書式を設定できるわけです。次の節でそのやり方を見ていきましょう。書式設定データ.format.ps1xmlの書式についてはMSDNにリファレンスがあるので、これを読めば自分で一から作成することは可能です。それはちょっと面倒くさいので、既存の書式設定データをベースに、独自型用に修正していくのがお勧めです。書式設定データはGet-FormatDataコマンドレットで取得でき、Export-FormatDataコマンドレットでファイルとして出力できます。Export-FormatDataコマンドレットの出力XMLファイルは改行コードが入っていなくて見づらいので、XmlDocumentとして再度読み込んでSave()するという小細工を施すのがお勧めです。先ほどのProcessクラス(System.Diagnostics.Process)の書式設定データをファイル化するには以下のようなスクリプトを実行します。$ps1xml))このスクリプトを実行すると、Processクラスの書式設定データをprocess.format.ps1xmlファイルとして出力できます。ISE(ただしv3の)で開くのがお勧めです。XMLノードを折りたたみできるので。出力した.format.ps1xmlファイルをつらつらと眺めると、実際の出力書式の定義はビュー(View)という単位で行われていることがわかります。View要素の下にはName要素(ビューの名前)、ViewSelectedBy要素(ビューを反映する対象の型)、TableControl要素(表の書式)があります。TableControl要素の下にはTableHeaders要素とTableRowEntries要素が含まれており、前者は表のヘッダーに記載するラベルやその幅などをTableColumnHeader要素に一つ一つ定義し、後者は表の本体に表示するオブジェクトのプロパティ値をTableColumnItem要素に一つ一つ定義しています。TableColumnItem要素は単純にプロパティ値を表示させるならPropertyName要素にプロパティ名を書くだけでOKです。スクリプトの結果を表示させるならScriptBlock要素内にスクリプトを書きます。ScriptBlock要素内で自動変数$_に1オブジェクトが格納されています。結局のところ、表に表示したいプロパティの分だけ、TableColumnHeader要素(プロパティ名のラベル)とTableColumnItem要素(プロパティ値)を1:1で定義していけばOKです。以上を踏まえてprocess.format.ps1xmlを改変して、前回作成したWinscript.Driveクラスの書式設定ファイルdrive.format.ps1xmlを作成してみました。64このように定義した型のオブジェクトに対しても、綺麗な書式で出力することができるようになるわけです。まとめここまで全三回にわたって、「関数の定義」「型の定義」「出力書式の定義」の基本のきについて説明してきました。基本とはいえ、PowerShellでがっつりとちゃんとした関数を書く上で真っ先に押さえておかないといけないことばかりですし、逆にここまで必要最小限に絞った記事もあまりないかなと思い、まとめてみました。参考にしていただければ幸いです。PSアドベントカレンダー2012もこれで終わりです。皆様、よいクリスマス...はもう終わりなので、よいお年を!※終わりと言っておきながら実は明日以降、ロスタイムとしてもうひとかたご登場の予定です。2012の14日目の記事になります。前回(アドベントカレンダー1日目)は「PowerShellらしい関数の書き方」と題して、パイプライン内でうまく他のコマンドと連携させるための関数をどう書けばいいのか、ということについて書きました。前回の関数の例では入力型と出力型がstringだったのですが、実際は自分で定義した型を入力、出力値に取るように書くのが普通かと思います。今回は、それをするためにどうやって型を定義するのか、そしてその型を関数にどう指定するのか、という話をします。PowerShellにはクラス定義構文がないそもそもの話になるんですが、型を定義する、つまりはクラスを記述するためのPowerShellのステートメントやコマンドレットが無いため、PowerShell単独ではできません。無理です以上おしまい。...というわけにはいかないので、実際はどうするのがいいのかという話をしていきます。方法としては大きく分けて二つあると思います。1.C#など他の.NET言語を用いてクラスを記述する2.ユーザー定義オブジェクトを作成する今回は1の方法を説明します。C#を用いてクラスを記述するつまりはPowerShellでクラスを定義できないなら、C#を使えばいいじゃない。2.0からはAdd-Typeというコマンドレットを用いると、C#やVBなど.NET言語のソースをその場でコンパイルしてアセンブリとして現在のセッションに読み込むことが可能です。論理ドライブを表すDriveというクラスを定義してみます。CSharpVersion3このようにC#のコードを文字列として-TypeDefinitionパラメータに与えると、コンパイルされて指定のクラス(ここではWinscript.Drive)がロードされます。3.0の構文が利用できます。2.0でも正しく動作します。3.0でコンパイルするためです(逆にPSv3でC#2.0でコンパイルするには”CSharpVersion2”という新しく追加されたパラメータ値を指定します)。ここでは-TypeDefinitionパラメータを用いてクラス全体を記述しましたが、この例のように列挙体も定義してそれをプロパティの型にするなどせず、すべて基本型のプロパティで完結するのならば、-MemberDefinitionパラメータを使ってメンバ定義だけを行う方が記述が短くなります。以下はWinscript.Manというクラスを定義する例です。CSharpVersion3例のようにC#のコード内には特にロジックを記述せず、単にデータの入れ物となるクラスにとどめておくのが良いかと思います。Studio使ってC#で全部コマンドレットとして書けばいいのに、ともなりかねないので。PowerShellでは実現困難な処理などがあればそれをメソッドとして書く程度ならいいかもしれません。メソッドを記述してもそれをユーザーに直接使わせるというよりも、関数でラップして使わせる形が望ましいでしょう。次はこのクラスのオブジェクトを扱う関数を記述していきます。定義した型のオブジェクトを扱う関数の記述ここでは3つの関数を定義しています。Get-Drive関数はシステムに含まれるすべての論理ドライブを取得、Show-Drive関数は指定のDriveオブジェクトをエクスプローラで開く、Set-Drive関数は指定のDriveオブジェクトのボリューム名(VolumeNameプロパティ)を変更するものです。Set)は、Get-Verb関数で取得できるリスト以外のものは基本的に使わないようにします。モジュールに組み込んだ場合、インポートのたびに警告が出てしまうので。関数の基本については前回に書いているので、今回のコードはそれを踏まえて読んでみてください。foreachステートメントではなくForEach-Objectコマンドレットを使うように修正。Get-*な関数のようにパイプラインの先頭で実行する関数でも、内部でPowerShellのコマンドレットや関数の出力を利用する場合は、配列化してforeachするよりも、ForEach-Objectで出力を逐次処理した方が良いですね。内部関数の出力がすべて完了してから一気に出力するのではなく、内部関数が1個オブジェクトを出力するたびに出力するようにできるので。}}細かい説明は省きますが、前回説明した関数の基本フォーマットに、自分で定義した型を適用してロジックを書くとこうなる、という参考例としてとらえてください。一つだけ前回に説明し忘れてたことがあります。それは[OutputType]属性です。これは文字通り、関数の出力型を指定するものです。この属性を指定しておくと何が嬉しいかというと、関数の出力を変数に代入したりWhere-Objectコマンドレットでフィルタをかけるコードを記述する際、関数の実行「前」にもプロパティ名をちゃんとタブ補完してくれるようになります。3.0からのものなので2.0だとできませんが、OutputType属性自体は2.0でも定義可能なので、定義しておくことを推奨します。型の定義と関数の定義をしたので実際に関数を実行してみます。#ネットワークドライブのみエクスプローラーで開く。取得結果はCSVとして出力。D:ドライブのボリューム名を指定。(管理者権限で)関数をきちんとPowerShellの流儀に従って記述したおかげで、このようにPowerShellの他の標準コマンドレットと同様の呼び出し方ができ、自作関数やそれ以外のコマンド同士をうまくパイプラインで繋げて実行することができています。一つ気になる点があるとすれば、ドライブの容量表示が見づらいということでしょう。容量であればGBとかの単位で表示してほしいですし、大きい数字は,で桁を区切ってほしいですよね。いう値を文字列で返すプロパティを定義してやる必要があるというかと言えばそんなことはなく、PowerShellには型に応じた表示フォーマットを指定する方法が用意されています。次回はそのあたりを解説しようと思います。C#とかめんどくさいしもうちょっと楽な方法はないのか?ということで、最初の方でちょっと触れた、ユーザー定義オブジェクトを利用する方法も、余裕があれば次回に。PSアドベントカレンダー、明日はsunnyoneさんです。2012が始まりました。初日は私が担当させていただきます。3.0の新機能!...ではなく、初心に返って、PowerShellの「関数」ってどう書くのがいいのかというお話をします。1.0から変わっていない基本の話です。これは今までずっと書きたかったネタですがなかなか書く暇がなくて放置してたものです。3.0の話はきっと他の皆さんが書いて下さるはず!私もまた順番が回ってきたら書こうと思います。PowerShellの関数は従来言語とだいぶ違うPowerShellを使いこなすようになってくると、他の言語を使う時と同じで、定型処理は関数として一つにまとめたくなってきます。ところが他の言語と同じような感覚で関数を書くと、どうもうまくいかないのです。引数にフォルダパスとフォルダ名を指定すると、指定フォルダが存在すればFalseを返し、存在しなければ作成してTrueを返す関数を書いてみました。NewFolderと呼び出せばいいんですが(まあ最初はここもつまづきポイントではありますが)、この実行結果は以下のようになります。NewFolderTrueフォルダが作成されてTrueが返却されることを想定していたのに、なんか余計な出力が混じってしまっています。なんでしょうこれは?実はPowerShell関数内で値が出力されると、returnキーワードがついてなくてもすべて呼び出し元に出力されるという仕様なのです。PowerShellにおけるreturnキーワードの効果は「後続処理を打ち切って呼び出し元に戻る。returnの後に値が指定してあればそれを最後の値として戻す」となります。そのため、呼び出し元に返したくない出力が関数内にある場合は、すべて[void]にキャストしたり|Out-Nullとしてリダイレクトするなどして出力を破棄する必要があるのです。ように破棄してやる必要があるわけです。先ほどの例を見ると、「いやいやなんでそんな訳のわからない仕様なんだよ、returnあるときだけ値返せよ」とお思いかと思います。これはPowerShellの特長の一つである、コマンドのパイプラインによる連携を行うための仕様なんです。ここでコマンドを繋ぐパイプラインがどういう動作をしてるか、おさらいします。{$_.Path}これはハンドル数が500以上のプロセスのメインモジュールファイルのパスを取得するというコマンドで、別に何の変哲もありません。稼働中のすべてのプロセスの一覧を配列として取得する。�@で取得した配列を走査して、Handlesプロパティの値を調べる。Handlesが500以上のオブジェクトだけ抽出した配列を生成する。�Aで生成した配列を列挙して、{}内のスクリプトをそれぞれ実行する。稼働中の1つのプロセスオブジェクトを取得して次のコマンドへ送る。そのプロセスのハンドル数が500以上なら、次のコマンドへ送る。そのプロセスに対して{}内のスクリプトを実行する。未取得のプロセスが残っていれば�@に戻る。いう動きをしています。パイプラインの手前で一旦すべての処理を終えてから、出力オブジェクトがまとめて配列という形で次のコマンドに送られるのではなく、オブジェクトがパイプラインの先頭から末尾に向けて1つずつ通過していき、それが先頭コマンドの出力オブジェクト数だけ繰り返される、という動作をしているのです。これがPowerShellのパイプライン処理が、従来の処理系での関数と決定的に違うところで、パイプラインによって複数のコマンドが、あたかももとからあった単一のコマンドのように密に連携するわけです。(この処理、.NETのLINQにちょっと似てると思う方もいらっしゃると思います。LINQとは全然違うものです。類似点も多いのでいずれ比較なんかを書きたいと思ってます)パイプラインで連携可能な関数の書き方さて、先ほどのパイプラインの話ではコマンドレットを連携させていました。PowerShellにおいてはコマンドレットも関数も、それが.NETのクラスかPowerShellのスクリプトなのかの違いがあるだけで、基本は同じ「コマンド」です。関数もコマンドレットと同様、適切な記述をおこなえば、パイプラインでコマンド同士を連携させることが可能です。以下に、Get-Repeatという関数の例を挙げます。この関数は-Textパラメータに文字列を指定し、-Countパラメータに回数を指定すると、指定文字列を指定回数分連結した文字列を出力する、という何の変哲もない関数です。パイプラインからの入力を受け付け、次のパイプラインへ出力することを想定した作りになっています。}}以下は実行例です。2ababcdcdこのように、パラメータに値を指定してもパイプラインから入力しても、スカラー値(配列ではない単一のオブジェクト)でも配列でも、正しく処理されています。この関数をポイントごとに見ていきましょう。PowerShellの正式な関数はparam節、beginブロック、processブロック、endブロックに分かれます。param節にはパラメータを指定します。beginブロックにはパイプラインで連携した際、最初の1回だけ実行される初期化処理、endブロックには最後の1回だけ実行される後始末処理を記述します。beginとendは今回の例では内容を省略しています。processブロックには、パイプラインから入力された1つのオブジェクトに対してその都度実行される処理を記述します。ちなみに、コマンド�@|コマンド�A|コマンド�Bとある場合、各コマンドにおけるbegin,process,endブロックは次のような順番で呼び出されます。コマンド�@begin→コマンド�Abegin→コマンド�Bbegin→{コマンド�@process→コマンド�Aprocess→コマンド�Bprocess→コマンド�@process...}→コマンド�@end→コマンド�Aend→コマンド�Bendprocessブロックでの処理は、通常はパイプラインだけではなくパラメータからも値を入力できるようにしておきます。今回のパラメータには「このパラメータは必須である」を意味するMandatory=$trueもあわせて指定しています。ようにパラメータの型を配列型にしておくことで、どちらを指定しても処理できるようにしています。processブロックではパラメータ経由で配列値が渡された場合に、各要素に対して処理を行うためforeachループを設けています。ちなみにスカラー値が渡された場合もforeachは問題なく処理します。processブロック内では、returnは記述しません。returnするとその時点で関数が終了してしまうので正しくすべての出力ができなくなってしまいます。この例の関数のように入力型と出力型が同一の場合は、processブロックでは1オブジェクトの入力に対して、1オブジェクトを出力するようにしておくと、他のコマンドと連携させやすくなります。Where-Objectコマンドレットのようにフィルタ処理を行う関数の場合は、条件によっては何も出力しないようにします(空の配列とか$nullを返すのではないことに注意)。入力オブジェクトから何らかの配列値を出力する場合もありえます。最低限、これらのポイントを押さえて関数を記述すると、他のコマンドとパイプラインで連携しやすい、PowerShellらしい関数を書くことができると思います。まとめPowerShellでは従来言語と同じ感覚で関数を書くと、うまくいかないことが多いです。処理をひとまとめにしたいというニーズだけならばそれでも問題ないのですが、関数同士を組み合わせたいときに問題が顕在化します。パイプラインの真の動作を理解し、パイプラインの中に組み込んで動作させることを想定した関数を記述すると、他のコマンドレットあるいは自作関数と連携しやすくなり、PowerShellの真の力を解放することができると思います。2012の1日目にしてはえらい固いネタかもですが、基本をおさらいするのも大事ですよね。明日は@jsakamotoさんの番です。|Tweetコンピュータの再起動はRestart-Computerコマンドレット、シャットダウンはStop-Computerコマンドレットを使えばできますが、(休止状態ではなく)スリープはどうやるんだろう?と疑問に思い調査しました。COMコンポーネントのShell.ApplicationのSuspendメソッドを使う方法:少なくともうちのWin7環境では使えませんでした(何も起こらない)。shutdown.exeを使う方法:/hオプションを使えば休止状態にはできるが、スリープはできない模様。powrprof.dll,SetSuspendStateを使う方法:ハイブリッドスリープが有効である場合は休止状態になる。Sleep”でスリープするというのは嘘tipsです。xcaqhbajのメモWMIのWin32_OperatingSystemのWin32Shutdownメソッドを使う方法:シャットダウン、再起動、ログオフはできますがスリープや休止状態はできないようです。いうわけであまり簡単にはいかないようです。2.0からはAdd-TypeコマンドレットによりC#やVBを介してP/Invokeができますので、これを利用してスリープを行うWin32APIを呼び出すことにしました。$func::SetSuspendState($false,$false,$false)powrprof.dllに含まれるSetSuspendState関数をP/Invokeで呼び出してやります。引数のHibernateはTrueを指定すると休止状態、Falseを指定するとスリープになります。今回はスリープしたいので$falseを渡してやります。メソッドを使うのでもいいですね。こちらの場合はAdd-TypeコマンドレットでSystem.Windows.Formsを読み込むと利用できるかと思います。Add-TypeコマンドレットのおかげでPowerShellからWin32APIを簡単に呼べるようになり、○○はWin32APIを使わないといけないから諦めよう、ということがなくなりました。WSH時代に比べると良くなったなーと思います。本当はなるべくならWin32APIを直接は使わずに片づけたいところですけども。2011の13日目、そして私の2回目の記事となります。今日のテーマは前回の続きで、PowerShellのバックグラウンドジョブの結果を読み取ったり、バックグラウンドジョブに値を与えたりして、ジョブと通信を行う方法を解説します。ジョブから呼び出し元に値を返却するジョブの結果を取得するにはReceive-Jobコマンドレットを使用すれば良いと前回書きましたが、今回はジョブ側から結果を返す実際の方法を示します。ブロック)でパイプラインを通じて最終的にデフォルト出力に渡されたすべての値です。複数行に渡って出力されている場合は、呼び出し元にはその配列(object[])として返却されます。ジョブにおいてもそれは同様で、基本的にStart-Jobなどで生成したスクリプトやスクリプトブロックが出力したすべての値がジョブの出力となり、呼び出し元からはReceive-Jobコマンドレットで受け取ることができます。以下に現在の日付時刻を出力するサンプルを示します。サンプルなのでジョブなのに同期的な処理になってますがご了承を。$job|Receive-Job複数だと以下のようになります。3種類のオブジェクトが出力されますので、結果は長さ3のobject配列になります。そのためこれらの値を個別に取り出す場合は次のようにします。$result[2]このように配列のインデックスで各値にアクセスできますが、これだと受け取り側での処理が分かりにくいと思われるかもしれませんね。そこでお勧めなのが、このように複数値を返却するのではなく、カスタムオブジェクトを1つだけ返却するようにする方法です。$result.Numberこの方法ではジョブの中でNew-Objectコマンドレットでカスタムオブジェクトを作成し、それを返却しています。返却値は1つのオブジェクトでそのプロパティに値が格納されているのでドット演算子で値を参照できるようになりました。この方法にも欠点があって、Receive-Objectで結果を参照するとき、ジョブが終了するまですべての値が参照できません。実はジョブが完了してない段階でも、Receive-Objectを実行するとジョブがそこまで出力した値を逐次取得することができるのです。1+1}のようにしてジョブを走らせた後、適当な間隔で$job|Receive-Jobを実行すると、それまでに出力した部分までを取得して書き出します。先程の例のように出力をカスタムオブジェクトでまとめてしまうとこの手法が使えなくなってしまいます。どちらもメリット、デメリットがあるのでうまく使い分けると良いかと思います。具体的にはジョブの実行途中では結果を取得せず、ジョブ完了後の最終的な結果のみまとめて参照したい場合はカスタムオブジェクトで返却し、それ以外はそのまま随時値を返却するようにすればいいと思います。ジョブの結果を受け取る際にもう一点注意しなければならないことがあります。それはジョブが返すオブジェクトの型です。PowerShellのジョブ機能はリモーティング機構の上に構築されているというのは前回も書きましたが、その関係上、呼び出し元とジョブとの間でオブジェクトを受け渡しする場合は一度シリアル化され、受け取り側でデシリアライズされます。オブジェクトのクラスもしくは構造体がシリアライズ可能(Serializable属性がついている)なら、PowerShellによりシリアル化→デシリアライズされたオブジェクトはシリアル化される前のオブジェクトと同一のものです。ないオブジェクトの場合だと完全に元と同じオブジェクトには復元されません。(Get-Process)[0]をジョブで実行するとSystem.Diagnostics.Processオブジェクトが得られますが、それをジョブの呼び出し元に返却するとDeserialized.System.Diagnostics.Processというカスタムオブジェクトに変換されます。このオブジェクトは各プロパティ値は(シリアル化可能なものだけ)保持しているものの、メソッド定義などは消失しているのでこのオブジェクトのメソッドを実行することはできません。ちなみにSystem.StringクラスやSystem.Int32やSystem.DateTime構造体はSerializable属性がついているのでジョブの結果として取得しても元のオブジェクトと同一なので、メソッドなどが呼び出し可能です。ジョブに呼び出し元の値を渡す今度は逆の場合です。ジョブを走らせるとき、呼び出し元からジョブに値を渡す方法です。$job|Receive-JobこのようにStart-Jobコマンドレットの-argumentListパラメータに、ジョブに渡したい値を指定すればOKです。複数ある場合はこのように配列指定も可能です。ジョブ側ではparamキーワードで仮引数を指定しておけば、スクリプトブロック内で呼び出し元の値が格納された変数を使用できます。ここではparamを使いましたが、paramを使用しない場合は$argsに実引数が配列として格納されているので、これを利用するのでもOKです。値を渡す場合でもシリアライズとデシリアライズが行われるので、その点だけは注意が必要です。ジョブは呼び出し元と別インスタンスなので、呼び出し元に読み込まれた関数を参照することはできません。よってジョブでも呼び出し元で定義した関数を実行したい場合は同様に-argumentListで関数の実体であるスクリプトブロックを送ってやる必要があります。$job|Receive-Job-argumentListでスクリプトブロックを渡すとStringにキャストされてしまうので、ジョブ内でそれをCreateメソッドでスクリプトブロックに戻してから実行演算子&で実行するという回りくどいことになってしまいました。関数にこだわらなければ呼び出し側でスクリプトブロックを作って変数に入れ、それを-argumentListに入れてやると少しだけ記述がシンプルになりますが、ジョブ内でスクリプトブロックを復元しなければならないのは同様です。いずれにせよあんまり美しくないのでお勧めしません。-InitializationScriptパラメータの中で関数やスクリプトブロックを定義してやるか、関数を別スクリプトファイルに切り出して、そのスクリプトファイルをジョブ内で読み込むほうが良いかと思います。前者の場合だと呼び出し元とジョブ内で関数を共有することはできませんが、後者の方法だとファイルとしては分割してしまいますが可能です。おわりに今回はジョブと通信する方法として、ジョブから結果を出力したり、ジョブに値を渡したりする方法をまとめました。意外と落とし穴が多いので注意してください。このシリーズはあと1回だけ続く予定です。@mutaguchiBooks書籍版。