開発現場でPowerShellを使う

4/2にTech Fieldersセミナー「スクリプトを使用した Windows Server 管理の自動化」でライトニングトーク(五分貰って好きなこと喋る)をしてきたので、しゃべった(つもりの)内容を置いておきます。あまり緊張しないほうだと思ってたんだけど、やっぱ緊張するなー。一応書きたいことを羅列したものは作っておいたんだけど、きっちり五分でしゃべる内容決めといたほうがいいんだろうな。あたりまえだけど……。ちなみに、ライトニングトークでは最後に投票をして、一番票を集めた人がマイクロソフトのプレゼン用マウスをもらえるんですが、貰えなかったので次出ることがあったらもっと改善して票を集めるのを目標に挑みたい……。

ということで、PowerShellを開発現場で使って、簡単・便利なツールを作る方法についての自分なりのコツを紹介したいと思います。

この図は最近まで開発してたシステムです。.NET Frameworkとしては、標準的な構成かな、と思います。DBがあって、Webサービス経由が、あって、UIとして、ASP.NETか、Windowsフォームのアプリケーションがあります。
PowerShellではテスト用のツールなどを作っていたんですが、今日紹介するのは、図の1.と2.の部分です。
まず、1.のツールなんですが、これは特定の条件でDBからデータを取得し、ファイルに落とし込みます。そして、任意のタイミングでDBに書き戻します。これはどういう用途に使うかというと、

  1. テストをしてデータが書き換わったときに元に戻したい
  2. 本番環境からテスト環境にデータを持ってきたい

なんて時に使います。

もう一つの下側、2.ですが、これは、ログからリクエストを拾ってきて、Webサービスのリクエストを再現するのに使います。リクエストはシリアライズしてログに記録してるので

  1. Webサービスのテストに使う
  2. 本番のリクエストをテスト環境で再現する
  3. いろいろなマシンで同時に実行すれば負荷テストも出来るかも(やったことないけど)

とかって使い方になります。

どちらも、データやリクエストはシリアライズしてXMLでファイルにしているので、データを一部エディタで書き換えたり、コピーして微妙に違うものを作るなんてことが出来ます。

具体的な作り方を説明します。この二つのツールは非常に似通ったつくりになってるので、そのつくりを図にしてみました。

Webサービスへのリクエストを行ったり、DBデータの取得、反映などの、肝になる動作はほとんどほとんどVisual Studioが自動生成するソースでできてしまいます。
Webサービスで言うと、WSDLを参照してプロキシクラスを作ってくれるんですが、そいつにデシリアライズしたオブジェクトを渡すだけです。DBだったら、型付DataSetとテーブルアダプタです。

ただ、そのままだとPowerShellから扱いにくいので、使いやすいレベルで動作をまとめて、C#なり、VB.NETなりで、スタティックメソッドにまとめてしまいます。スタティックメソッドにするのはPowerShellから呼び出しやすいからです。

スタティックメソッドに渡す引数は、基本的にXMLシリアライズしてファイルに落とし込んだオブジェクトをデシリアライズして使います。XMLなのでDBに反映したい中身や、Webサービスに渡したいリクエストの中身をエディタで編集もできて便利です。

スタティックメソッドは呼び出しが楽とはいっても、さすがにそのまま使うのはなんなので、PowerShellのfunctionでラップします。実際にツールとして使うときは、この関数を呼び出して使う形になります。

基本的に、上で紹介したようなツールはほとんど自動生成されるソースを、C#でちょこちょこっとラップしてあげるだけで出来てしまいます。簡単・便利なのでぜひご利用ください……。

ちなみに、自作のアセンブリ、DLLでもEXEでもかまいませんが、を読み込んでPowerShellから利用するには、

[System.Reflection.Assembly]::LoadFile({ファイル名})

オブジェクトをXMLシリアライズしてファイルに落とすのは、

 $s = 'System.Xml.Serialization.XmlSerializer'
 $f = 'System.IO.FileStream'

 $slzr = New-Object $c -A @({クラス名})
 $fs = New-Object $f -A @({ファイル名}, 'Create')
 try { $slzr.Serialize($fs, {シリアライズ対象}) } finally { $fs.Close() }

シリアライズ

 $fs = New-Object $f -A @({ファイル名}, 'Open')
 try { $o = $slzr.Deserialize($fs) } finally { $fs.Close() } 

でできます。多分……