コマンドレットのパラメータを列挙してみる。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の15日目です。前回はPowerShellのASTの概要を解説しました。今回は前回の補足というか応用的な内容になります。前回、スクリプトブロックからどのようなASTが生成されるのか、図で書きました。そもそもあの図を作るにあたって、ASTの構造を視覚的に把握したかったので、そのためのスクリプトを書きました。PowerShellで木構造を展開表示する方法は色々ある(※)かと思いますが、今回はJSONとして出力して、表示については他のアプリに任せることにしました。※Format-Customのデフォルトビューは意外と使えるただし、ASTオブジェクトをそのままConvertTo-Jsonコマンドレットに渡すわけにはいきません。いうのも、AST構造を再帰的に展開するには、探索の深さ(-Depth)を大きくしなければいけませんが、そうするとASTではないオブジェクトも逐一展開してしまい、現実的な時間内で終わらなくなってしまいます。そこで、ASTオブジェクトそのものをJSONにするのではなく、必要なプロパティのみ再帰的に取得したカスタムオブジェクトを生成し、それをJSONにする方針を取りました。その成果が以下のコードです。namespace節を使っているので、v5以上必須です。}}本来なら、50種以上あるAstクラスに応じてきちんと場合分けすべきなのですが、コードが長くなるだけなので、動的言語の強みを生かしてダックタイピング的な方法で下位ノードを再帰的に展開しています。Ast>>型であることを確認するのに苦労してますが、多分もっといい方法があると思います...。他はAstオブジェクトそのものか、ReadOnlyCollection<Ast>を返すだけなのでそんなに苦労はないです。Ast抽象クラスに含まれているExtent、Parentプロパティ以外で、Astを要素に含まないプロパティに関しては、ASTの葉として解釈しています。次にこのスクリプトを使って、スクリプトブロックをJSONとして出力します。ast.jsonサンプルとして用いるスクリプトブロックは、前回のものと同じです。これを先ほど書いたGet-Ast関数に渡して、結果をConvertTo-JsonでJSON化しています。この際、探索の深さを100としていますが、ネストが深いスクリプトブロックなどでは、もっと大きくする必要も出てくるかもしれません。Viewerを使って表示してみたのが、以下のスクリーンショットになります。色んなスクリプトのASTを表示して、楽しんでみてください。ASTシリーズはもう少し続きます。Visitorと静的解析のお話です。2015の15日目の記事です。はじめに前々回と前回は、PowerShellによるWebスクレイピングの具体的手法についてまとめました。APIを用いるのが本道かと思います。APIを用いるお話です。APIというのは、その名の通り、プログラムからWeb上のデータを取得したり、何らかのサービスの機能を実行したりするための、呼び出し方式を定めた規約です。APIでは、HTTPリクエストに呼び出したい機能の内容を指定し、結果をHTTPレスポンスとして受け取るというのが一連の流れになります。APIの主な実装方式としてはSOAPとRESTがありますが、このうち、XMLでリクエストを組み立てるSOAPは最近は廃れてきた感じです。APIはNew-WebServiceProxyコマンドレットで対応しています。APIを指すことが殆どです。DELETE:データの削除)、操作に対するパラメータ=クエリストリング(GETの場合)もしくはリクエストボディ(POSTの場合)、結果の返却=HTTPレスポンス(JSON、XML等)となるのが基本です。RESTの呼び出しは基本的にステートレスなものとなります。要はセッション情報を持たない≒cookieを使わない、ってことです。APIを簡便に利用するためにInvoke-RestMethodコマンドレットが用意されています。3.0から)Invoke-RestMethodコマンドレットのパラメータ指定Invoke-RestMethodコマンドレットのパラメータについては、実は前々回に取り上げたInvoke-WebRequestコマンドレットと同じです(IEのパーサーを使うことはないので、-UseBasicParsingも無いですが)。APIの形式は前述の通りなので、利用するパラメータは限られてきます。パラメータ(連想配列あるいはJSONやXML等)となるかと思います。@{Authorization="認証情報"}のような指定も必要になることがあります。Invoke-RestMethodコマンドレットのレスポンスInvoke-RestMethodコマンドレットがInvoke-WebRequestコマンドレットと異なる最大のポイントは、レスポンス文字列の種類によって、自動的に出力オブジェクトの型が切り替わるところです。私の調べた限りでは以下のような対応になっているようです。APIでAEDの所在地情報を検索し、JSONで結果を得ることができます。-AutoSizeのようにします。ここで$responseには、JSON形式のデータをパースしてPSCustomObject化したデータが格納されるので、あとはFormat-Tableコマンドレットで見やすい形で出力してあげれば良いでしょう。結果はこんな感じです。MAP上で表示する」なんてこともできます。APIにQueryStringでパラメータ(経度、緯度)情報を渡しているところと、レスポンスから生成されたオブジェクトのプロパティ値をマップ表示の際のパラメータとして利用しているところに注目してください。APIの一種と考えて良いと思います。ここではこのブログのRSSを取得する例を示します。Format-ListDescriptionの加工がやや適当(HTMLタグっぽいところを削除して50文字に切り詰めてるだけ)ですが、少し見やすくしています。結果は以下のように表示されます。RSSの結果は、1エントリがXMLElement型のオブジェクトとして出力されるので、データの取扱いが比較的楽だと思います。APIの良い例がなかったので省略してますが、基本的には前回取り上げた、XHTMLをXMLとしてパースする方法と同じやり方です。Invoke-RestMethodの場合は[xml]型アクセラレータによる変換は不要で、いきなりXmlDocumentオブジェクトが得られます。現状の問題点レスポンスがコールバック関数つきのJSONP形式であるとかで、JSON、XML、RSS/ATOMのいずれの形式にも適合しない場合はプレーンテキストとして出力されてしまいます。その場合は、出力文字列を適宜加工した後に、[xml]型アクセラレータや、ConvertFrom-Jsonコマンドレット等により手動でオブジェクト化するようにしてください。その場合は敢えてInvoke-RestMethodを使わずInvoke-WebRequestで充分ですが。APIというのは大抵(特にPOSTの場合)、認証を要するのですが、最近よくあるのはTwitter等でもおなじみのOAuth認証です。ところがOAuth認証は結構めんどくさい処理で、何らかのライブラリを使わないとしんどいです。残念ながらPowerShellの標準コマンドレットには存在しないので、自前で頑張って書くか、既存のライブラリやコマンドを利用することになるかと思います。今回そこまで説明できませんでしたが、また機会があれば。2015の10日目の記事です。はじめに前編では、Invoke-WebRequestコマンドレットやWebClientクラスを用いて、WebページからHTMLの文字列を取得するところまで説明しました。後編の今回は、取得したHTML文字列をパースして、オブジェクトとして利用可能しやすい形に変換する話です。IEエンジンによるHTMLパース(DOM)前編でも触れましたが、Invoke-WebRequestコマンドレットは、レスポンス文字列を取得すると同時に、HTMLをパース(構文解析)し、結果をオブジェクトとして構造化してくれます。Explorerのエンジンを呼び出すことで実現されています。(ちなみに後で説明しますが、-UseBasicParsingパラメータを付与すると、IEエンジンを使わずごく基本的なパースのみ行うようになります。Model)に従ってアクセスすることができます。(-UseBasicParsing指定時は不可)HTMLのtable要素を切り出し、table各行を1オブジェクト、各セルをプロパティとして、オブジェクト配列化する例を以下に示します。Format-Listところで前編で軽く触れましたが、IEエンジンによるパースは、Invoke-WebRequestコマンドレットを用いずとも、以下のようにして直接IEのCOMインターフェースを呼ぶことで利用可能です。以下同様...というより実際に試すと直接IEエンジンを呼び出す方がずっと速いです。理由はよく分かりませんが...。HTML要素コレクションの取得Invoke-WebRequestコマンドレットを用いると、DOMとは別に、すべての要素(AllElementsプロパティ)、input要素(InputFieldsプロパティ)、img要素(Imagesプロパティ)、a要素(Linksプロパティ)、script要素(Scriptsプロパティ)を含むコレクションを、HtmlWebResponseObjectオブジェクトの対応するプロパティからそれぞれ取得することができます。コレクションに含まれる各要素は、innerText(タグ内の文字列)、innerHTML(タグ内のHTML)、tagName(タグ名)等のプロパティが共通して利用可能です。要素の属性(たとえばa要素ならリンク先を示すhref属性)に、プロパティとしてアクセス可能となります。以下はBingでWeb検索した結果から、ページタイトルとURLを抜き出す例です。HtmlWebResponseObjectのLinksプロパティでa要素の配列を取ってきて、次に検索結果では無いっぽいURLを、hrefプロパティの値を見てwhereで除外し、最後にinnerTextプロパティとhrefプロパティをTitle、Urlとリネームしてから値を出力しています。泥臭い処理が混じってますが、この泥臭さがスクレイピングなのかもなぁと思います。Format-Listform要素についてもほぼ同様にFormsプロパティからコレクションを取得できますが、このコレクションにはFormObjectという特別なオブジェクトが含まれます。FormObjectのFieldsプロパティは、Key=パラメータ名、Value=パラメータ値が格納された連想配列となっています。この連想配列は書き替えが可能なので、前編で説明した、ログオンを要するWebサイト等で用いると便利かと思います。以下に、HtmlWebResponseObjectオブジェクトのプロパティをまとめます。×このように、一部のプロパティについては-UseBasicParsing指定時でも利用可能です。サーバーOS等でIEエンジンが利用できない場合には-UseBasicParsingパラメータが必須となりますが、その場合でも最低限のパースはしてくれるわけです。HTML要素のコレクションを利用する方法は、DOMを使う方法に比べると自由度は少ないですが、「ページから画像のリストを取得したい」等の処理は簡便に行うことができます。最後に、Invoke-WebRequestコマンドレットとIEエンジン以外のHTMLパース手法について軽くご紹介します。XMLとしてパース(XHTML限定)XHTMLというのはごくかいつまんで言うと、HTMLをXMLで定義したものです。XHTMLはXMLなので、XMLとしてパースして用いることができます。PowerShellは[xml](XmlDocument)型アクセラレータと型アダプタにより、XML要素への簡便なアクセス手段を提供しています。以下のように、[xml]型アクセラレータを用い、取得したXHTML文字列を[xml]型に変換すると、以降は型アダプタの機能により、ドット演算子で要素を辿っていくことができます。[xml]$content$xml.html.body.h2.'#text'ただ世の中のWebページ上のXHTML文書が、すべてXML文書としてvalidなものであるかと言われると、現実はかなり厳しいです。XML文書としてエラーがある場合は、型アクセラレータの処理は容赦なく失敗します。この手法は「使えたら強いが、大抵使えない」レベルのものと思って頂ければいいと思います。SgmlReader標準機能にこだわらなければ、.NET製のHTMLパーサーを使うのが楽かと思います。SgmlReaderは通常のHTML文書(当然、XHTMLに限らず)をXmlDocumentへとパースしてくれるので、PowerShellと相性が良いのではないかと思います。以下にサンプルを載せておきます。{$_.p.innerText}ぎたぱそ氏も以前SgmlReaderを取り上げておられるので、そちらも参考にして下さい。tech.guitarrapc.cóm正規表現等で自前パースこれまではHTMLパースを既存のコマンドやライブラリを用いて行ってきましたが、対象のHTMLが非常にシンプルである場合とか、HTMLですらなく単なるテキストの場合だとか、対象ページは分量が多いものの必要箇所はごくわずかで、かつピンポイントに取得可能な場合等々は、むしろ自前でパースするコードを書いた方が手っ取り早いこともあります。$Matches[1]}のようなコードで十分かと思います。Parsingの実装であるConvertFrom-Stringコマンドレットを用いて、HTMLパースができないかな、と考えています。Weblogまとめ前後編に渡って、PowerShellでのWebスクレイピングの手法について解説しました。APIが用意されていない場合の苦肉の策ですが、背に腹は代えられない場合というのは稀によくあると思います。いうときに今回の記事が参考になれば幸いです。APIがちゃんと用意されてる場合に、PowerShellから利用する話をやろうかと思います。|TweetTwitterでこんな問題を出してみました。PowerShell検定中級編。以下を実行するとそれぞれ何が起こるか。2015以下、解答になります。空のスクリプトブロック{}を実行演算子&で実行しています。空なので何も出力はありません。ブロックがありません。」というパーサーのエラーになります。解説PowerShellのスクリプトブロックは、beginブロック、processブロック、endブロックを内包します。スクリプトブロック直下にparamブロック、DynamicParamブロック、beginブロック、processブロック、endブロック(他にもあったかも)以外のステートメントを記述すると、Endブロック内に記述されたものと暗黙的に解釈されます。この場合、スクリプトブロック直下にprocess...と書き始めたので、パーサーはprocessブロックが開始されたと判断しますが、続くステートメントブロック{}(≠スクリプトブロック)の記述がないため、構文エラーとなります。解説パーサーは�Aのように解釈しますが、今回はステートメントブロック{}がきちんと記述されているので、エラーなく解釈されます。processブロックは、パイプライン入力がない場合でも1回実行されますが、この場合、中身は空なので、�@と同様、何も出力はありません。&{process{process}}結果Get-Processコマンドレットが実行され、プロセス一覧が表示されます。�Bまでの解説の通り、&{process{...}}とすると、...の部分が1回実行されます。今回はprocessブロック内に「process」と記述しているので、�Aのようなパーサーエラーは発生せず、「process」がステートメントとして実行されます。PowerShellのステートメント(文)には「For」とか「If」とかと並列して、「パイプライン」が存在します。「パイプライン」には1つの「式」もしくは複数の「コマンド」が含まれます。Name」という2つのコマンドが含まれます。(ちなみに、「式」とは「$x+1」とかの、値を返すもののことです。PowerShellではパイプラインの最初の要素にのみ、「コマンド」ではなく「式」を記述することができます。)今回のお題では、「process」はprocessブロック下に記述されており、ForやIf等のステートメントではないのでパイプラインとして扱われます。このパイプラインには1つの要素のみ含まれていますが、式ではないので、コマンドとして解釈されます。・コマンド探索の挙動PowerShellの「コマンド」は、関数、コマンドレット、ワークフロー、Configuration、ファイル(実行ファイル、スクリプトファイルを含む)、&演算子で実行するスクリプトブロック等が挙げられます。コマンドの探索は、まずコマンドへのエイリアスを探します。ない場合は、関数名orコマンドレット名を探します。それでもない場合は、実行ファイルやスクリプトファイルの拡張子(.exe、.ps1等)を付与してパスの通ったディレクトリを探します(ちなみにカレントディレクトリにあったとしても、相対パスor絶対パス表記でない場合は実行しません)。ここからが「本当は怖い」ところなんですが、ここまで探索してコマンドがなかった場合、与えられたコマンド名に"Get-"を付与してもう一度探索します。今回のお題では、processという名前のコマンドを探して、もしパスが通ったフォルダにprocess.exeとかがあればそれが実行されますが、ない場合はGet-Processというコマンド名を探します。Get-Processというコマンドレットは標準で存在するので、それが実行されてしまう、というわけでした。Discoveryにより未ロードのモジュールを探します。すると調べられるので、見てみるのもいいかもしれません。ブロックとして指定され、入力が存在しないためです。ブロックは、入力を使用せずに評価できません。」というParameterBindingExceptionが発生します。・パーサーの挙動Get-Processが実行され(ようとす)る理由については�Cまでの理解でOKでしょう。Get-Processコマンドレットには-Nameという、プロセス名を指定する位置パラメータが存在します。位置パラメータは、パラメータ名を指定せずパラメータ値のみを指定しても、指定順にパラメータにバインドしてくれる機能を持ちます。powershell」が実行されます。今回のお題「process{}」は、パーサーによってまず、コマンド名「process」と、パラメータ値「{}」(空のスクリプトブロック)に分割されます。(ちなみにコマンド名に「{}」を含めることができないわけではなく、そういうコマンドを実行したい場合は、`でエスケープするか、&"command{}name"のように&演算子を用いれば可能です。)今回の場合、パラメータ名の指定はありませんが、位置パラメータ-Nameに空のスクリプトブロックがバインドされることになるわけです。・コマンドパラメータバインドの挙動さて、-Nameパラメータの型は、System.String[]であり、scriptblockではありません。scriptblockからSystem.String[]への暗黙の型変換はありません。エラーメッセージ的には、スクリプトブロックを与えたこと自体は咎めていないように思えますね。実はこれ、スクリプトブロックパラメータと呼ばれてる機能です。詳しくはスクリプトブロックパラメータのススメを見ていただくとして、要はコマンドへのパイプライン入力を、指定のスクリプトブロックで処理し、その出力結果をパラメータ値としてバインドする機能ですね。今回エラーになった理由は、スクリプトブロックパラメータとして解釈しようとしたが、そもそも入力がなかったから、ということになります。意味はないですが、以下のように入力を与えてやれば、スクリプトブロックパラメータとして動作します。{$_}この場合パイプライン入力が追加されるので、-Nameパラメータの指定位置がずれることになるので、パラメータ名が必要になります。その引数の入力によって出力が作成されなかったためです。」というエラーをご丁寧に出してくれます。Trace-CommandでParameterBindingソースをトレースしてみるのも一興でしょう。ちなみにあまり関係ない余談ですが、-NameパラメータにはValueFromPipelineByPropertyName属性が付いているので、実は以下のような指定もできます。Get-Processまとめ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話を聞かせて下さい。|Tweet昨日10/11のPowerShell勉強会#4にお越しいただいた方、どうもありがとうございました。スタッフ一同、これからも定期的に開催していこうと考えておりますので、ぜひともよろしくお願い致します。私のセッション資料を公開します。くま横浜で行ったものとほとんど同じですが、v5関係のスライドを少しだけ修正しています。今回は高度な関数とコマンドレットの作り方を主に取り上げていますが、要するに、PowerShellコマンドはPowerShellスクリプトでもC#でもほぼ同じようにして同じようなものが書ける、ということなのです。基本を押さえればどちらにも対応可能です。両者は状況に応じて使い分ければOKかと思います。PowerShellよりC#が得意、というならばコマンドレットを書くと良いですし、スクリプト的なお手軽なコードで書きたい場合は高度な関数、とかですね。速度が求められる部分とか、.NETアセンブリを多用する部分だけコマンドレットにして、他を高度な関数にする、あるいは、主要部はコマンドレットにして、その他はコマンドレットのラッパー的な高度な関数を用意する、のように両者を組み合わせたモジュールもよくあります。以下はデモ用のサンプルスクリプトです。高度な関数の雛型的なものになります。使い方はスライド本文を参照してください。}}以下はデモで用いたC#のコードです。コマンドレットクラスの雛型的なものになっています。ビルドの際はSDKに含まれるPowerShell関係のdllを参照設定してください(詳しくはスライド)。実行してインポートして下さい。以下の例だとGet-Baz、Set-Bazの2コマンドレットがインポートされます。|Tweet小ネタですが。ダイナミックパラメータについてはぎたぱそ先生の記事を参照してください。要は、その名の通り動的に定義されるパラメータのことです。ダイナミックパラメータは他の(静的な)パラメータの指定状態によってリアルタイムに定義されます。ValidateSet(値の候補リスト)だけではなく、パラメータの有無、パラメータ値の型やパラメータ名ですら変わり得ます。Get-Commandコマンドレットを使うとコマンドのパラメータや構文等の情報を取得する事ができます。ダイナミックパラメータは前述のような特性があるため、パラメータの有無、パラメータ名、パラメータ値の型が一意に定まりません。この問題を解決するため、Get-Commandコマンドレットには-ArgumentListパラメータが用意されています。指定コマンドに与えるパラメータ値を-ArgumentListパラメータに指定すると、指定コマンド実行時にそのパラメータ値を指定した場合に定義されるダイナミックパラメータに関する情報が、出力結果に含まれるようになります。Get-Contentコマンドレットの-Pathパラメータは「位置パラメータ」、つまりパラメータ名を省略できるパラメータであり、パラメータ名なしで指定された一つめの値がバインドされます。するとC:\(FileSystemプロバイダのパス)が-Pathパラメータにバインドされるわけです。同じ意味となる)ところでGet-Contentコマンドレットは、FileSystemプロバイダでのみ有効となる-Encodingというダイナミックパラメータを持っています。「FileSystemプロバイダでのみ」というのはつまり、「カレントディレクトリがFileSystemプロバイダのパスであるか、-PathパラメータにFileSystemプロバイダのパスが指定されたとき」ということになります。入力した瞬間、-Encodingダイナミックパラメータが定義されて利用できるようになります。Get-CommandコマンドレットでGet-Contentコマンドレットの-Encodingダイナミックパラメータの情報を得るにはどうすればよいか。答えは、このダイナミックパラメータが定義されるトリガーとなるパラメータ値である「C:\」を-ArgumentListパラメータに指定してやればいいわけです。C:\を実行するときに定義される-Encodingダイナミックパラメータも含まれていることがわかります。[<commonparameters>]のように表示され、ダイナミックパラメータが表示されないことが分かるかと思います。-ArgumentListは配列指定もでき、複数パラメータ値を指定した時の構文を調べることもできます。位置パラメータ以外の場合、つまりダイナミックパラメータ定義のトリガーとなるパラメータにパラメータ名を指定する必要があるコマンドの場合はどうやら無理のようです。ダイナミックパラメータのリストを表示します。-ArgumentListは指定してないので、カレントディレクトリのプロバイダの種類でのみ結果が変わります。}}まあ本題とあんまり関係なくなってしまったんですが、せっかく作ったのでということで。いうのを見ると、PowerShellはコマンドもオブジェクトなんだ、ということが分かると思います。guitarrapc.wordpress.comというエントリを上げておられます。ループを回して定期的に出力するとTableが一つにまとまらない、とのことですが、パイプラインの先頭で無限リストを出力するようにすればOKです。あとデータの再利用を考えるなら、最初からCSVで出力しておくのが無難かと思います。画面出力と同じものをファイル出力するだけで良いなら、Tee-Objectコマンドレットでもいいかと。ついでに本体の関数も何となく短くしてみました。Format-Table以上。記事のとおり、自分の全ツイートデータをダウンロードする機能がTwitterで利用可能になっています。ダウンロードされるzipファイルには、ツイートを表示するためのHTML、JavaScriptファイルのほか、CSV形式のデータ(tweets.csv)も含まれています。CSVファイルの処理といえばPowerShellが得意とするところです。このファイルを読み込んで、PowerShellで自分のツイートを分析してみましょう。具体的にダウンロードする方法は上記記事を参考にしていただいて、まずはダウンロードしたzipファイルからtweets.csvを解凍し、PowerShellのカレントディレクトリをtweets.csvのあるフォルダに移動させておいてください。毎回CSVを読み込むと時間がかかるので、まず以下のようにしてImport-CsvコマンドレットによりCSVファイルを読み込み、変数にオブジェクトとして入れておきます。tweets.csvなお私の総ツイート数は4万ほどで、tweets.csvは10MB程です。これくらいの容量だとそのままでもまずまずまともな速度で分析が可能ですが、何十万ツイートもしていらっしゃるTwitter廃人マニアの方は、適宜ファイルを分割するなどして対処願います。CSVファイルのヘッダ行は"tweet_id","in_reply_to_status_id","in_reply_to_user_id","retweeted_status_id","retweeted_status_user_id","timestamp","source","text","expanded_urls"となっています。Import-Csvコマンドレットはデフォルトでは1行目を出力オブジェクトのプロパティ名とするので、データ行の1行がtweet_idプロパティ等を持つオブジェクトとして読み込まれ、$tweets変数にはそのオブジェクトの配列が格納されることになります。:Select-Objectコマンドレット(エイリアスselect)はオブジェクトの絞り込みに使います。このCSVファイルではツイートの並び順がタイムスタンプの降順なので、最初のツイートは一番最後の行となります。Speedがなんか懐かしい。いえばEAのシムシティ不具合お詫び無料DL特典、何選ぼうかなあ。シティ4あるけど英語版という噂だし2013やった後につらいもんがありそう。いうわけでシムシティ大好きなんで、私の街を返してください......Format-Listコマンドレット(エイリアスfl)を使うと必要なプロパティ値のみ抽出してリスト形式で表示できます。眠いのってもしかしてアレルギールの副作用かも。眠いなあ...Where-Objectコマンドレット(エイリアスwhere)を使うとオブジェクト配列のうち特定条件のもののみ抽出できます。ここではツイート本文(textプロパティ)に"眠い"という文字列が含まれているものを抽出しています。家族でおせちをたべた。おいしかった...もちろん本文に含まれる文字列以外にも、timestamp(ツイート時刻)で抽出するなどもできます。ここではtimestampがGMTで分かりづらく、かつ文字列のため扱いづらいので、Select-Objectに集計プロパティを指定してDateTime型に変換しています。Format-ListやSelect-Objectに指定する集計プロパティの書式は、@{L="ラベル";E={値を返すスクリプトブロック}}のように連想配列で指定します。LはLabel、EはExpressionのように省略せずに指定してもOKです。集計プロパティはあんまり解説を見かけないですけども、オブジェクトを処理するコマンドレットの多くで利用可能できわめて重要なので覚えておくと良いと思います。$_.timestamp}},text応用でこんなんもできます。0:00:00ちょうどのツイートを抽出します。私はかつてよるほ成功したことがないので結果は何も返ってきませんけど。expanded_urlshttp://ja.wikipedia.org/wiki/%E5%B2%A1%E7%B4%A0%E4%B8%96http://htn.to/4oxXDNhttp://guitarrapc.wordpress.com...whereによる抽出を応用するとこういうこともできます。expanded_urls列は本文中のURLが複数含まれているとそれらは,で区切られるため、可変長の行となります。Import-Csvコマンドレットはこのような可変長なCSVに対応していないので、複数URLがあっても最初の1つのみ取得します。それとexpanded_urlsが追加されたのはt.coによるURL短縮が始まってからなので、昔のツイートにこの値は含まれていません。2012/09...ツイート分析と言えばやはりツイート数統計を取ることから始まるでしょう。統計を取るにはGroup-Objectコマンドレット(エイリアスgroup)が使えます。ここでもグループ化キーとして集計プロパティを指定してやります。ツイートの「年/月」を文字列化し、それが同じツイートでグループ化することで、月別ツイート数の統計が表示できるわけです。23やり方としては先ほどのとほぼ同じです。-ExpandPropertyはパイプライン入力でオブジェクトのプロパティ値を取得できるのでよく使います。プロパティ名」でも取れますね。Sort-Objectコマンドレット(エイリアスsort)でもソートキーとして集計プロパティを指定できます。ここではNameプロパティ(グループ化キーの値)をintに変換したものをキーにソートしています。Saturdayこれもやり方としてはほぼ同じ。ソートキーはDayOfWeek列挙体にキャストしてちゃんと曜日順に並ぶようにしてます。:ここからはツイート数の計測をしていきます。単純にツイート総数を取るだけならMeasure-Objectコマンドレット(エイリアスmeasure)を使うだけでOKです。Averageなどは対応するスイッチパラメータ(-Averageなど)を指定すると計測されますが、この場合は元オブジェクトが数値ではないのでエラーになります。TextLengthツイート文字数を計測するとき、元のオブジェクトにはツイート文字数を返すプロパティはないので、Select-ObjectコマンドレットにTextLengthという集計プロパティを指定して新たに作ってしまいます。Measure-Objectコマンドレットは-Propertyパラメータにより対象オブジェクトのどのプロパティ値を計測するか指定できます。私の総ツイート文字数は183万です。リツイートの場合はretweeted_status_idにリツイート元のツイートIDが入るので、このIDがあるものはWhere-Objectで除外してます。ツイート本文の<や&などはHTMLエンコードされたものがtext列に格納されているので、HttpUtilityを使ってデコードしてから文字数をカウントしています。0.023254609488295Measure-Objectコマンドレットは計測方法を指定することはできないので、独自の計測を行う場合はこんな感じでコードめいたものを書く必要が出てくるかと思います。RT率たったの2%か...ゴミめ...ForEach-Object(エイリアスforeach)は1個のスクリプトブロックをパラメータに指定するとprocessブロック相当の列挙部分を実行しますが、このように3個指定すると、それぞれbegin(初期化処理)、process、end(終了処理)ブロックに割り振られます。ここではbeginブロックで変数初期化、processブロックで通常ツイートとリツイートを加算、endブロックで計測値をPSObjectに格納して出力してます。3.0ではカスタムオブジェクトを作る場合は「[pscustomobject]@{連想配列}」で書くほうが楽です。{$count}137覚えてないから数えます。数値だけを出力するならこんな感じでシンプルに書けますね。twigadge...ここからはいろんなランキングを取得してみます。ツイートに使ったTwitterクライアントのランキング。ここでもGroup-Objectを使っています。クライアント名はクライアント配布URLがaタグで含まれているのでそれを-replace演算子で削ったものをグループ化キーとしています。ランキングなので最後はCountで降順ソート。Countのパターンになるかと思います。リプライツイートはin_reply_to_user_id列にリプライしたユーザーIDが含まれるのでまずはそれでフィルタし、ユーザー名はツイート本文から取ります。ユーザー名は-match演算子を使って正規表現で抽出します。$matches自動変数は連想配列で、[0]にマッチ全体が、[1],[2],...にはサブ式のキャプチャが入ります。ちなみにサブ式に名前を付けてるとキー名が数値ではなくサブ式名となります。#techedj2009...ハッシュタグも同様のアプローチで取れますが、ハッシュタグは1ツイートに複数あることがあり、-match演算子だと複数のマッチは取れないので[regex]を使って取得しています。おわりにPowerShellのオブジェクト処理用コマンドレットを用いると、CSVデータの分析ができます。普通はログファイル等を解析するのに使うわけですが、こういう身近なデータを扱ってみるのも面白いんじゃないでしょうか。PowerShellの勉強にもなると思います。@mutaguchiBooks書籍版。