コマンドレットのパラメータを列挙してみる。2017の10日目です。PowerShellはオブジェクトを扱うシェルですが、別にテキストデータを扱えない訳ではありません。PowerShellで取得したデータをテキストファイルとして保存したり、スクリプトで用いるデータをテキストファイルで保存しておくことは日常的に行われることだと思います。PowerShellで扱うデータはオブジェクトであり、テキストファイルは文字通り文字列であることから、コマンドレットを用いる等、何らかの手段で変換が必要になります。テキストデータ形式にも様々な種類があり、それぞれメリット、デメリットが存在します。今回の記事では、PowerShellで用いるデータを保持しておく際のテキストデータ形式について比較をしてみます。プレーンテキストプレーンテキスト、すなわち書式なしのテキストファイルです。シンプルな使い方をする場合、文字列配列の各要素に含まれる文字列が、テキストファイルの1行と対応します。UTF8$linesは文字列変数です。理由がなければ文字コードはUTF-8で良いと思います。UTF8Add-Contentは実行のたびにファイルを開いて、書き込んでから閉じるという動作をするので、1行ずつforeachで実行するのはNGです。UTF8)メリット文字列配列をテキストファイルに書き出すのは多分これが一番楽だと思います。書き出したデータは人間にも読みやすい。デメリット文字列だけを保存しておきたいというケースがそもそも少ない。CSVコンマ等の特別な文字で区切り、1行あたりに複数のデータを保存できる形式です。PowerShellのコマンドレットで扱う場合、オブジェクトが持つプロパティがヘッダ列名に対応します。各行にオブジェクト配列1要素のプロパティ値が、カンマ区切りで保持されます。Default$objectsは任意のオブジェクト配列です。必要であればSelect-Objectコマンドレットを併用して、プロパティを絞り込みます。文字コードはExcelでそのまま読み込み/書き出しができるDefault(日本語環境ではShift_JIS)がお勧めです。Defaultメリットオブジェクトのプロパティ値が、すべて数値あるいは文字列で表現できる値を持つ場合に最も適合する。人間にも読みやすく、ある程度は編集もできる。デメリットオブジェクトのプロパティが、数値と文字列以外のオブジェクトである場合、すなわち、階層構造を持つデータの保存には適さない。数値も文字列として読み込まれてしまうので、数値として扱いたい場合は変換が必要になる。Export-CsvとImport-Csvで扱うCSVファイルはヘッダが必須。ヘッダなしのCSVファイルが既にあって、それを読み書きするという用途には適さない。(できなくはないが)書き出し時の列順を制御することができない。PowerShellで書き出したCSVを、列順が固定であるとの想定である他のプログラムで読み込むことは基本NG。書き出し時、1つ目の要素に存在しないプロパティは、2つ目以降では存在しないものとして扱われる。同種のオブジェクトで構成される配列なら通常は問題ないのだが、要素によって動的に追加されるプロパティがあったりなかったりすると厄介。(ADでありがち)JSONJavaScriptのような表記でデータを保持するデータ形式です。データの受け渡しに様々な言語で利用できます。APIでもよく利用されます。PowerShellではv3からJSONを扱うコマンドレットが提供されています。ConvertFrom-JsonメリットCSVと異なり、階層構造を持ったデータでも扱える。CSVと異なり、数値は数値型のまま読み書き可能。(整数値はint、小数値はdecimal)人間にもまぁまぁ読めるし、頑張れば編集できなくもない。デメリット-Depthパラメータによりプロパティを展開する階層の深さを指定はできるが、プロパティに応じて深さ指定を変化させるというようなことはできない。基本的には、自分で構築したPSCustomObjectを使うか、JSON化する前に自分で元オブジェクトを整形しておく必要がある。直接ファイルに書き出し、追記、ファイルから読み込みするコマンドレットはない。実は細かい話をしだすと色々と罠があります...。CLIXMLPowerShellではPSリモーティング等、プロセス間でオブジェクトのやり取りを行う際に、CLIXML形式を介してシリアライズ/デシリアライズが実行されます。シリアライズ対象によっては、完全に元のクラスのオブジェクトに復元されます。(復元されないオブジェクトにはクラス名にDeserialized.との接頭辞が付与され、プロパティ値のみ復元される)ユーザーもコマンドレットを用いて、任意のデータをCLIXML形式でシリアライズし、XMLファイルとして保存することができます。file.xmlメリット元のオブジェクトの構造、プロパティ値と型情報を含めてほぼ完全にテキストファイルに保存できる。復元したオブジェクトはプロパティ値を参照できるのはもちろん、オブジェクト全体が完全にデシリアライズされ、元の型に戻った場合には、メソッドを実行することも可能。元の型に戻らず、Deserialized.との接頭辞が付いた状態でも、コンソールに表示する場合は元の型のフォーマットが使われるので見やすい。デメリットすべてのオブジェクトが元の型に戻せるわけではない。戻せるかどうかは確認が必要。人間が読み書きするようなものではない。ちなみに、ConvertTo-Xmlという似たようなコマンドレットがありますが、出力形式はCLIXMLではない上、復元の手段もなく、かといって別に読みやすいXMLというわけでもなく、正直何のために使うのかよく分かりません(適切なxsltでも用意すればいいのかな?)。ConvertTo-Htmlの方が使えそうです。psd1psd1は「PowerShellデータファイル」で、モジュールマニフェストやローカライズデータに使われるファイル形式です。コメントなど基本的な言語要素のみ使用可能です。5.0以降ではImport-PowerShellDataFileコマンドレットを用いて、任意のpsd1ファイルのデータを読み込み、変数に格納することが可能です。書き出し書き出し用のコマンドレットはありません。読み込み例えば以下のような内容をbackup_setting.psd1として保存しておきます。ルート要素は必ず連想配列にします。"0:00"}なお、dataセクションで全体を括ってもいいですが、psd1で許容される言語要素はdataセクションより更に制限がきついので、敢えてしなくてもいいんじゃないかと思います。このファイルは以下のように読み込めます。backup_setting.psd1$settingには連想配列が格納され、以下のように値が参照できます。$_.To}メリットPowerShellの構文でデータを記述できる。通常のps1ファイルを呼び出すのとは異なり、式の評価やコマンド実行などはされない分、セキュアである。配列と連想配列の組み合わせにより、JSONライクな階層構造を持てる。JSONとは違い、コメントが入れられる。デメリット記述できるデータはプリミティブなものだけ。スクリプトから書き出すためのコマンドレットがない。一応、下位バージョンでやる方法はあります。まとめPowerShellで扱うデータをテキストファイルとして保存する際には、各テキストデータ形式の特性を理解し、メリット、デメリットを踏まえて選定する必要があります。当然ながらテキストファイルに保持することが不適切なデータもありますので、そこは注意してください。(画像データを敢えてBase64とかでエンコードしてテキストファイル化する意味があるのか、とかですね)個人的には...ちょっとした作業ログ等を記録しておきたい→プレーンテキスト.NETオブジェクトの一部のプロパティだけ抜き出してファイル化したい→CSV自分で構築したPSCustomObjectをファイル化したい→JSON.NETオブジェクト全体をファイル化したい→CLIXMLスクリプトで使う設定データを用意したい→psd1みたいな感じでなんとなく使い分けていると思います。psd1はまだ採用例はないですが...。今回はビルトインのコマンドレットで扱えるもののみ取り上げましたが、他にもyaml等のテキストデータ形式が存在し、有志によるモジュールを用いて扱うことが可能です。2016の20日目です。はじめに前々回はASTの概要について述べ、最後にAST.FindAllメソッドを使って、ASTから指定のASTノードを検索する方法について説明しました。前回はASTを再帰的に検索して、木構造を視覚化してみました。Visitorを用いる方法について説明します。お断りしておきますが、静的解析の実装までは今回はたどり着きません。静的解析ツールをどう作るかorどう作られているか、ということを雰囲気で味わっていただければと。Visitorの説明をする前に、まず、Visitorパターンについて簡単に。Visitorパターン[Wikipedia]というのは、オブジェクト指向言語におけるデザインパターンの1つで、対象オブジェクトを巡回する「訪問者」クラスを定義するものです。Visitorクラスでは、対象クラスごとに行う処理を、個別にvisitメソッドをオーバーロードさせることで定義します。共通のVisitor抽象クラスを継承することで、異なる機能を持ったVisitorクラスを作ることができます。処理対象クラスには、Visitorオブジェクトを引数に受け取る、acceptメソッドを定義します。acceptメソッドでは、引数として受け取ったVisitorオブジェクトのvisitメソッドを呼ぶことで、処理を実行させます。処理対象クラスが子要素クラスを持つ場合には、acceptメソッド内で、子要素クラスのacceptメソッドを呼ぶようにします。しておくことで、Visitorは処理対象を再帰的に巡回できるようになります。このように処理対象クラスから、実際に処理を行う機能をVisitorクラスとして分離することで、処理対象クラスに手を加えることなく、Visitorクラスを追加して、処理内容を増やしたりすることが可能になります。Visitorの呼び出し方を見ていきましょう。Ast抽象クラスには、以下の2つのVisitメソッドが定義されています。astVisitor)説明に入る前に注意点。メソッド名は"Visit"となっていますが、Visitorパターンでいうところの"accept"メソッドのことです。メソッド名がAcceptじゃないのかは不明ですが...。AstクラスのVisitメソッドは、AstVisitor抽象クラスを継承したクラスのオブジェクトか、ICustomAstVisitorインターフェースを実装したクラスのいずれかを引数に取ることで、ASTに対する処理を実施します。AstVisitor抽象クラスを継承、もしくはICustomAstVisitorインターフェースを実装することで、ASTの種類に応じた巡回処理を行うクラスを、自分で定義していきます。AstVisitor抽象クラスAstVisitor抽象クラスは、Visitorとしての基本的な機能があらかじめ実装されています。具体的には既に以下の機能は用意されています。ASTの種類に応じたVisitメソッドの定義すべての種類のASTに対応するVisitメソッド(50個以上)がVirtualメソッドとして定義されています(※)。IfStatementAstに対する処理を行うための、VisitIfStatementメソッドがあります。※一般的なVisitorパターンでは、Visitメソッドを対象クラス分オーバーロードさせますが、PowerShellのAstVisitorは対象クラスに応じた別名のメソッドを定義する方式です。これも理由は分かりませんが、オーバーロードにするには多すぎるからかもしれません。各Visitメソッドには、ASTの子ノードに対し、再帰的にVisitメソッドを呼ぶ仕掛けがあらかじめ備わっています。各VisitメソッドはAstVisitAction列挙型を返却します。以下のように返却する値によって、ノード巡回の継続、停止を制御できます。Continue:ノード巡回を継続(デフォルト)SkipChildren:子ノードの巡回を行わないStopVisit:巡回を終了するカスタムAstVisitorクラスを作成する以上の基本的な機能を踏まえて、AstVisitor抽象クラスを実装したカスタムVisitorクラスを作ります。v5で追加された、クラス構文を使って書いてみましょう。「利用しているコマンドのリストを取得する。コマンドのパラメータ内で別コマンドを呼び出している場合は除く。」というお題を解くことを考えます。ASTのFindAllメソッドだと、配下に含まれるすべてのCommandAstを取得してしまうので、単純にはいきません。そこでカスタムAstVisitorクラスの出番です。このお題を実現するVisitorクラスは以下のようになるでしょう。}}PowerShellのクラス構文において、Virtualメソッドのオーバーライドは、単に同名のメソッドを定義するだけですので、ここではVisitCommandメソッドをオーバーライドします。プロパティとフィールドの区別はないので、コマンド名の一覧を格納するCommandNamesプロパティは上記のような定義になります。メソッド内でクラスメンバを参照する際には$thisを用います。作成したGetCommandNamesVisitorクラスをインスタンス化し、解析対象スクリプトブロックのASTのVisitメソッドに引数として渡します。GetCommandNamesVisitor$scriptBlock.Ast.Visit($visitor)$visitor.CommandNames実行すると、結果はGet-ChildItemSort-ObjectSelect-Objectのようになるかと思います。AstVisitorクラスの具体的な実装については、PSReadLineやPowerShellEditorServicesにありますので、参考にしてみてください。前項で述べた、AstVisitor抽象クラスを継承したカスタムAstVisitorクラスの場合、基本的な処理を実装する必要はないですし、目的とするASTクラスに対するVisitメソッドだけオーバーライドすればいいので、非常に簡便です。本格的にPowerShellの構文解析を行いたい場合、ノードの巡回順だとか、その他もろもろをもっと細かく自分で実装したいケースが出てきます。いった場合にはICustomAstVisitorインターフェースを実装したクラスを作って対応します。ICustomAstVisitorインターフェースも、AstVisitor抽象クラス同様、各ASTクラスに応じたVisitメソッドが定義されているのですが、各VisitメソッドはAstVisitAction列挙体ではなく、object型のオブジェクトを返します。自分で好きなオブジェクトを返すように定義できるわけです。Ast.Visit(ICustomAstVisitor)はAstVisitor抽象クラスを引数に取る場合と異なり、objectを返却するのですが、このとき返却されるのは、最初に実行されたVisitメソッドの戻り値になります。ICustomAstVisitorはインターフェースですので、処理はすべて自分で定義しなくてはなりません(※)。ノードの再帰的探索も、必要ならもちろん自前で実装する必要があります(前回紹介した、JSON化スクリプトのような処理になるかと思います)。※ISEだとインターフェースの実装を一発で行うリファクタリング機能はないので、今回みたく実装すべきメンバがたくさんある場合は、こんな感じのひな形を作るスクリプトを使うと良いでしょう。今回はICustomAstVisitorインターフェースを実装したクラスの実例まではご紹介できませんでしたが、興味のある方は、PSScriptAnalyzerで用いられているので参考にしてみてください。まとめPowerShellのASTについてきちんと解説している記事が英語圏を含めてもあまりないようでしたので、3回に渡って、一通りの基礎知識をまとめてみました。普通にPowerShellを使っている分には、滅多に使うことはないと思いますが、たとえばPSScriptAnalyzerのカスタムルールを自分で作る場合には、ASTの知識は必須になってきますので、必要に応じて参考にしていただければ幸いです。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さんの番です。|TweetPowerShellは@や$をよく使いますが、カッコとの組み合わせでたまに意味不明になってくるので、まとめてみました。組み合わせを網羅してると思います。1(System.Int32)と等しい。中の式が返す値を出力する。