tech.guitarrapc.cóm

Technical updates

WMF 5.0 (PowerShell 5.0) の再リリース予定が発表されました

WMF 5.0 (PowerShell 5.0) は、2015/12/16 にリリースされてから、12/23 に PSModulePath 上書きをはじめとするバグがあったことで回収されてました。

長らく再リリース日が未確定でしたが、本日リリース予定日として 2016年2月末日が予告されました。

blogs.msdn.microsoft.com

Updated 02/08/2016 – Thank you for your continued patience. We have fixed the offending PSModulePath issue and tested it thoroughly. We are working towards getting properly signed WMF 5.0 RTM builds. Now, we expect that around end of February you will be able to download the revised packages.

ということで、お待ちください。

PowerShell 5.0 で使える機能をまとめた記事も用意してあるので、リリースされ次第公開します。

WeatherHacks を触ってみる

Twitter を眺めていると面白そうなのをみつけたので、自分ならどう書くか考えてました。

https://www.baku-dreameater.net/archives/8741#more-8741www.baku-dreameater.net

ただやるのでは楽しくないので、PowerShell との比較です。

目次

コードサンプル

素直だったので、てきとーにいきましょう。エラーハンドルはここではしていません。

4つ用意しました。

gist.github.com

WeatherHacks.cs

元との違いは HttpClient 使ってることぐらいです。サクッとデータ取るにはいいのです。

WeatherHacksClass.cs

JSON をいちいちパースするのがつらいので、普段はクラスとして一気にデシリアライズしています。

Visual Studio2013 以降は JSON String を貼り付けるときに、自動的にクラス変換する機能があります。そこで、適当に PowerShell や LINQPad で取得したJSONをクリップボードにコピーして、VS2015 で貼り付けるだけで クラスが生成できます。

あとは普通です。

WeatherHacksClass.ps1

PowerShell v5 の クラス構文で C# に近いコードの再現例です。

PowerShell では Invoke-RestMethod Cmdlet で超お手軽簡単にAPIを叩いてオブジェクトとして取得できます。が、そのままでは型が PSCustomObjectとなりとてもつらくなります。これはクラスに結果を突っ込めば解消できるので、C# で取得したクラスをちょちょいと持ってくるか、JSON String をクラスに自動変換する何かを書けばいいでしょう。

ちなみに PasteJSONasPowerShell はダメです。PSCustomObject にしちゃうのでこのケースでは意味を成しません。

ConvertToClass がいい感じでしょう。*1

WeatherHacksFunction.ps1

クラス構文がない PowerShell v4 では、C# クラスを Add-Type するか、妥協して PSCustomObject でどうぞ。

まとめ

API を一発叩くだけなら、PowerShell でもいいのですが、いかんせん非同期処理はコールバック地獄一直線なので、非同期処理するなら C# 一択です。

*1:Class 名が重複した場合の処理が入っていないのでIssueにあげてあります https://github.com/dfinke/ConvertToClass/issues/2

Event Tracing for Windows (ETW) の トレースプロバイダーリストを取得してみる

ネットワークキャプチャといえば、Wireshark や Microsoft Message Analyzer が定番です。今回、USB や Bluetooth のキャプチャもできることを教わりました。

USBなど の通信ログは ETW (Event Tracing for Windows) に流れてくるのでEtwStreamでログをキャプチャできることも教わりました。そこで今回は、USB キャプチャなど各種Windows のトレースプロバイダーをEtwStream でキャプチャするために必要なトレースプロバイダー一覧を取得してみましょう。

このプロバイダーさえ把握できれば、USBに限らず自分で任意のトレースイベントを EtwStream でキャプチャできますからね!

目次

USB キャプチャ

先にキャプチャの様子です。 それぞれ Twitter で教わりました。

Wireshark

Wireshark をダウンロードしてインストールすれば利用できます。

Wireshark · Download

詳細は Wikiやググると豊富にあるので参照しましょう。

USB

Microsoft Message Analyzer

次は Microsoft Message Analyzer です。Windows におけるGUIキャプチャで実質最強なのは Wireshark ではなくMicrosoft Message Analyzerなのは多くの人が同意できるのではないでしょうか。Wireshark とか何年も触ってません。

Microsoft Message AnalyzerもUSB キャプチャに対応しています。

ダウンロードしてインストールは Wireshark より簡単です。

https://www.microsoft.com/en-us/download/details.aspx?id=44226

Microsoft から詳細動画が公開されているので見てみるといいでしょう。

Universal Serial Bus (USB) - Windows drivers | Microsoft Learn

EtwStream

そして、私たち開発者にとって一番うれしいのが EtwStream でも見れることです。

github.com

USBに限らずネットワークキャプチャもそうですが、通信にかかわる膨大なログから「狙いを付けて絞りこんで加工してみる」というのは手間です。Wireshark や Microsoft Message Analyzer の独自クエリは大げさかつだるいのですよね。そのため、EtwStream のように、自在にRxでグルーピングなどが容易にできてプログラムに組み込めるのは強力な長所といえます。普段 Fiddler をお使いの開発者にとっても思ったことはあるのではないでしょうか。

ETW Trace Provider

さてEtwStream の.FromTraceEvent(string[] providerNameOrGuid) はとても強力ですが、どのトレースプロバイダーかワカラナイとそもそもトレースできません。そして、トレースプロバイダーの指定はGUID.... ということで、各種プロバイダーの一覧を取得しましょう。

3種類用意しました。

  • logman
  • Get-NetEventProvider : PowerShell 4.0 (Windows 8.1 / Windows Server 2012 R2) から利用可能
  • Get-EtwTraceProvider : PowerShell 5.0 (Windows 10) から利用可能

gist.github.com

ただ、Get-EtwTraceProvider は、Name もでず GUID も無効なものが混じっているのでちょっと怪しいです。logman や Get-NetEventProvider にいらない管理者権限も必要なので、正直使わないです。

まとめ

EtwStream + Rx 最高です。Have a happy ETW life。

パイプラインの処理を途中で打ち切る方法のPowerShell版

PowerShell の最大の特徴と言われた時に、おそらく掲げるべきはパイプラインだと思います。

それが、cmd や Linux/Unix シェルにおけるパイプラインと異なる挙動だったり、オブジェクトを伝搬するという性質も含めて良くも悪くも PowerShell を PowerShell 足らしめているのはパイプラインかなと。

さて、パイプラインが特徴の PowerShell ですが、問題がいくつかあります。その1つが、以下の記事にあるパイプラインの中断処理。

d.hatena.ne.jp

winscript.jp

今回は少しその辺をみてみましょう。

目次

StopUpstreamCommandsException

先に結論だけ書くと、Internal なクラスであっても System.Management.Automation.StopUpstreamCommandsException が一番楽ちんでしょう。

ということで、PowerShell版で書くなら雑にこんな感じで。あえて New-Object ではなく Add-Type しています。

gist.github.com

中で System.Collections.Queue を使ってるのはそれほどの意味はありません。入力されたオブジェクトをいいように扱うためです。なので、カウンタ変数を用意してでもお好きなようにやればいいと思います。

この方式なら、Cmdlet でも同様なので好きなように扱えるのもいいでしょう。

はじめは、ReferenceSource見て自分で実装するのでいいんじゃないかなと思いましたが、当然ながら面倒さが上回ったのでやめました。

仕様変更あったらどうしよう

そもそもその場合は、Select-Object も仕様変わります。もはやその時点で一緒かなと。

Select -Fist 1 の GetSteppablePipeline() でも PowerShell スクリプトならいいのですが、Cmdlet から PowerShell 関数呼ぶの悲しさしかないですし仕方ないのかなという妥協もあります。

Connect

ちなみにこの Internal Class なやつを Public にしてというリクエストはあります。ワークアラウンドに do{}while()Select-Object -First 1 の例もあります。

Bing

すでにPowerShell のフィードバックは、UserVoice に移っており、同フィードバックも転載されています。

https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/11087865-enable-users-to-stop-pipeline-making-stopupstreamcwindowsserver.uservoice.com

なので、Stop-Pipeline Cmdlet や StopUpstreamCommandsException のパブリック化がこれに基づきされてほしいですね。Vote しましょう。

End が実行されないのも、フィードバックすればいいと思います。End で何かしらするのはリソース破棄以外にもあり得るので、実際あってほしいでしょう。PowerShell Team の書くスクリプトの中にも Process{} 句で配列にまとめて End{} 句で出力するパターンもあるので。

余談

こういったパイプラインの制限というか、まだまだいけてないシーンはあって、たとえば foreach(){} | .... もそれです。

https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/11087667-make-the-foreach-statement-work-with-a-pipelinewindowsserver.uservoice.com

パイプラインの中断エラーを Write-Error で出せるようにとかもあります。

https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/11088492-option-to-output-a-pipeline-terminating-error-viawindowsserver.uservoice.com

昔記事にした | Out-Null の遅さなども。

https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/11088471-performance-of-out-null-drastically-worse-then-usiwindowsserver.uservoice.com

tech.guitarrapc.com

あとは、Where-Object などで {} 抜きで自動変数 $_ にアクセスしたいという例など。実際これほしいですよね。

https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/11088306--should-be-accessible-without-curly-brackets-witwindowsserver.uservoice.com

まとめ

リフレクション最高 (

PowerShell Cmdlet を人道的に使いたいから頑張ってみるお話

この記事は、PowerShell Advent Calendar 2015 最終日の記事です。

https://atnd.org/events/72226atnd.org

最近はもっぱら C# を使っており、PowerShell も Cmdlet を書いてたりしてスクリプトあまり書いていません。*1 しかしながら、Cmdlet はただ読み込むならともかく、継続的デプロイを考えるとお世辞にも使いやすいとは言えません。むしろ鬼畜です。

そこで今回はPowerShell Cmdlet をもっと楽に頑張らず使えるようにするお話です。

目次

Cmdlet 概要

ここでいう Cmdlet(コマンドレット) とは、C# で書かれたPoweShell の処理です。普段よくググったりして見につくのは PowerShell で書いた関数です。この Cmdlet と関数についてはマイクロソフト自身表記揺れが見られますが、本ブログでは一貫してこの区分で説明しています。

そして、関数の定義されたファイル(.psm1) を読み込んだモジュールがスクリプトモジュール で、Cmdletの含まれるクラスライブラリ(.dll) を読み込んだモジュールがバイナリモジュールです。この辺の違いは2013年のアドベントカレンダーに書きました。

tech.guitarrapc.com

Cmdlet のメリット

Cmdletで書くメリットは数多くあります。最も強いのは書きやすさでしょう。PowerShell で書くよりも C# や F# で書くことが楽しいなら、Cmdlet はいいアプローチだと思います。

  1. C# や VB.NET、F# で書けることはそれ自体が大きなメリットです。いずれもPowerShell 関数よりも圧倒に処理を制御しやすく、非同期処理も書けます
  2. Nuget によるライブラリの組み込みも容易です
  3. バイナリモジュールは、スクリプトモジュール(平文)に比べて圧倒的なほど高速にモジュールが読み込まれます
  4. IL化されているため処理自体もスクリプトに比べて圧倒的に早くなることがほとんどです
  5. デバッグやテストなど開発シーンでは Visual Studio + C# の恩恵を受けられます。これは PowerShell + Visual Studio よりも多くの面で優位です

一見良いことだらけですが、スクリプトの方が楽なポイントもあります。

Cmdlet のデメリット

Cmdlet で書くデメリットは、アセンブリ化して発生するものです。つまり、基本的には Cmdlet は関数に比べてメリットが多くあります。

  1. 普通にCmdletを作っていると app.config が利用できない
  2. コンパイルされているため、スクリプトとは異なりモジュールを ISE で開いてオンザフライに修正などはできない
  3. バイナリモジュールを読み込むと、Cmdlet のクラスライブラリ(.dll) がファイルロックされてしまう

1は解決していますが記事にしていません。

2はどうしようもありません。が、そもそも継続的にデプロイする前提ではオンザフライな修正というのは本番環境ということで、必ずしもメリットにならない場合も多いでしょう。

今回は3 に関して解消する手段を考えてみましょう。 一度だけデプロイして以降は利用するだけなら「ファイルロック」はデメリットとなりません。いい例が、マイクロソフトの PowerShell Module 群です。しかし、こと継続的デプロイとなると話は別です。モジュールの利用中に .dll がロックされてしまうと、デプロイが失敗することになり、スムーズな継続的デプロイが実現できません。

バイナリモジュールの作成

今回のために、超簡易版のモジュール TestModuleを作成します。

gist.github.com

Cmdlet のクラスライブラリがファイルロックされるタイミング

PowerShell には、PowerShell module autoload という仕組みがあり、Import-Module <対象モジュール> を事前に実行せずとも $env:PSModulePath に配置されたモジュールを読み込んでくれます。これにより、インテリセンスや Get-Command で Function名/Cmdlet名が自動的に補完され、よりインタラクティブにモジュールが利用できるようになっています。

https://technet.microsoft.com/en-us/library/dd878284(v=vs.85).aspx

では、PowerShell module autoloadで クラスライブラリがファイルロックされるタイミングはいつでしょうか?Import-Module でモジュールを明示的読み込んだタイミングを除くと2つ考えられます。

  1. バイナリモジュールに含まれる Cmdlet がインテリセンスで候補に上がったタイミング
  2. バイナリモジュールに含まれる Cmdlet が実行されたタイミング
検証

Import-Module していない状態で、Get-Ho から Get-Hoge に自動的にインテリセンス補完されたタイミングでは、TestModule.dll は削除できています。

そしてGet-Hoge を実行するとファイルロックされました。このことから、Bのタイミングでバックグラウンドで Import-Module <対象モジュール> が実行され、クラスライブラリがファイルロックされていることがわかります。

ファイルロックを回避してモジュールを読み込ませる

あいにくと Import-Module に ファイルロックを回避して読み込んでくれる素敵機能はアリマセン。PowerShell からの支援はありません。

そこで考えられるのが、2つの手段です。

  1. 読み込ませるクラスライブラリの実体をデプロイパスから逃がす
  2. Assembly.Load(byte[]) を使ってバイナリとして読み込む

順に見ていきます。

事前準備 : モジュールの読み込み方法を工夫する

1,2 いずれの手段をとるにしても、Import-Module クラスライブラリ.dll ではモジュール読み込みの前後を制御できません。

そこで、マニフェスト(.psd1) と スクリプトモジュール(.psm1) を利用します。

読み込むモジュールのパスに、マニフェストモジュール(.psd1)、スクリプトモジュール(.psm1)、バイナリモジュール (.dll) が同時に存在した時の読み込み優先順位は次の通りになります。

  1. マニフェストモジュール(.psd1)
  2. スクリプトモジュール(.psm1)
  3. バイナリモジュール (.dll)

つまり、マニフェストモジュール(.psd1) でスクリプトモジュール(.psm1)を読み込むようにして、クラスライブラリ (.dll)をファイルロックしないようにモジュールとして読み込めばいいのです。簡単ですね。

.psd1 の生成

今回必要となる マニフェストTestModule.psd1 は、ビルド前イベントと連動して build.ps1 を実行することで生成してみましょう。

gist.github.com

うまくビルドできると、マニフェストファイル TestModule.psd1 が生成されます。

gist.github.com

.psd1のポイント

通常の バイナリモジュールでは、RootModule にクラスライブラリを指定しますが、今回はインポート処理自体をスクリプトモジュールでフックします。そのため、このマニフェストファイルでは、RootModule を TestModule.psm1 としています。

また、マニフェストファイルを利用した PowerShell module autoload への Cmdlet名のヒントとして、実際にユーザーが利用できる Cmdlet名を .psd1 にて明示するのが大事です。指定は CmdletToExport に配列でCmdlet名を入れましょう。これを忘れると、PowerShell module autoloadがクラスライブラリに含まれる Cmdlet を読み込めず、初回のモジュール読み込みでタブ補完が効きません。なお仕様として公開されていませんが、初回にモジュールからCmdletの一覧を読み込むと PoweShell はCmdlet一覧をキャッシュします。2回目以降は、例えクラスライブラリからCmdlet が読めなくてもこのキャッシュをタブ補完に利用するため、一見 Cmdlet が読めているように錯覚してしまいます。

では、.psm1 でクラスライブラリをロックしないように読み込む処理を書いてみましょう。

読み込ませるクラスライブラリの実態をデプロイパスから逃がす

あいにくとクラスライブラリのため、ShadowCopy は利用できません。%temp% パスに、 Import-Mopdule されるクラスライブラリをコピーするならこんな感じでしょうか。*2

gist.github.com

モジュール一式を$env:PSModulePath に含まれる %UserProfile%\Documents\WindowsPowerShell\Modules\TestModule にデプロイしました。さて動作をみてみましょう。

https://cloud.githubusercontent.com/assets/3856350/12003573/055e8954-ab68-11e5-8f53-f74baca1bd61.gif

モジュール読み込み後も$env:PSModulePath に配置したデプロイ対象のクラスライブラリは消せますね。これなら継続的デプロイに支障をきたしません。

モジュールが読み込まれる流れを説明します。実際のところ、モジュール読み込み時のログの通りです。*3

PS> Import-Module TestModule -Force -Verbose;
VERBOSE: Loading module from path 'C:\Users\UserName\Documents\WindowsPowerShell\Modules\TestModule\TestModule.psd1'.
VERBOSE: Loading module from path 'C:\Users\UserName\Documents\WindowsPowerShell\Modules\TestModule\TestModule.psm1'.
VERBOSE: Importing cmdlet 'Get-Hoge'.

以降の流れを追ってみましょう。

  1. モジュールが配置された状態でImport-Module TestModule を実行(Get-Hoge の実行でも一緒です)
  2. まず TestModule.psd1 が読み込まれる
  3. 続いてRootModule に指定した TestModule.psm1 が読み込まれる
  4. あとは、TestModule.psm1 で書いた通りに dll など一式をキャッシュパスにコピーして
  5. コピー先のクラスライブラリ TestModule.dll を直接 Import-Module する
  6. クラスライブラリをImport-Moduleしたことで、TestModule.psm1 のモジュール空間には Get-Hoge Cmdlet が含まれるので、それを含めて TestModule.psm1Export-ModuleMember を実行
  7. 最後に TestModule.psd1 がCmdlet を現在のスコープにインポートしている
課題

この方法には、毎回の Import-Module でクラスライブラリが %temp% にコピーされるという問題があります。

例えば、Remove-Module イベントと連動するModule.OnRemove にキャッシュを消すスクリプトブロックを仕込んで置くということも考えられますが、PowerShell ホストを直接 x 終了したらこの処理はスキップされてしまいます。

$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove

そこでゴミも出さずにきれいに読み込むことを目的に、Assembly.Load(byte[]) を利用してみましょう。

Assembly.Load(byte[]) を使ってバイナリとして読み込む

知られてませんが、PowerShell のクラスライブラリは、別に Import-Module でパスを指定せずとも、Assembly.LoadFrom() で取得したアセンブリを渡してもモジュールが読み込まれます。*4これを利用すれば、クラスライブラリをファイルロックせずにモジュール読み込みができます。

実装は次のサイトが参考になります。

Creating instance of a type outside the current AppDomain

あとはこの処理を PowerShell で実装して、Assembly.LoadFrom()Assembly.Load() にするだけだです。具体的には次のコードとなります。

gist.github.com

動作をみてみると問題なくモジュールが動作しますね。また、モジュールを読み込んでも、TestModule.dll はファイルロックされていません。

https://cloud.githubusercontent.com/assets/3856350/12003631/bdd7d952-ab6a-11e5-97ee-e5f61be6a094.gif

まとめ

Cmdlet の最大の問題である、Import-Module によるクラスライブラリのファイルロック問題をなんとかしてみました。本当は、app.config を使う方法や、スクリプトモジュール同様の .ps1 をコンフィグレーションファイルに利用する方法も書こうと思ったのですが、まずはここまでで。

なお、まだ諦めてない模様 (むしろ PoweRShell 5.0 で書こうと画策している。

www.adventar.org

*1:むしろ LinqPad の方が使ってます

*2:nuget の利用などを考慮すると面倒なのでまとめてコピーしている

*3:クラスライブラリからのモジュール読み込み部分は、Loading として表示されていません

*4:この時、クラスライブラリは通常バイナリモジュールとして読み込まれるところが、ダイナミックモジュールとして読み込まれます。