amatarasu48 の紹介

シアトル在住の日本人ソフトウェアエンジニアです。心は常に日本にあります。よろしくお願いします。

テキストファイルに含まれている文字列を検索

ずっと前にPowerShellでテキストファイルに含まれる文字列を検索して検索する文字があったらそれを画面上に表示するというのを紹介したことがあったが、もうちょっと踏み込んで条件に合ったファイルを一つのディレクトリにコピーするところまでやってみよう。

dir -filter *.sql -recurse | where {[System.IO.File]::ReadAllText($_.FullName) `
-match "DataStatus"} | foreach {$_.CopyTo("D:\Temp\$_")}

上の例では拡張子がsqlのファイルを最初にリストしてDataStatusという文字列が含まれるとD:\Tempにそれぞれのファイルをコピーするというもの。必要に駆られたのでメモ程度に紹介しておきます。

PowerShellからWatiNを使う

PowerShellからウェブアプリテスト用のフレームワークWatiNを読み込んでウェブアプリのテストプログラムを書こうとしているのだが、これが一筋縄ではいかない。二重、三重の問題が立ちはだかる。

まずはWatiNをダウンロードしてWatiN.Core.dllをPowerShellに読み込む。そしてIEを立ち上げてURLに行くという単純な作業をしたいと思い、まずは下のようなコードを書いてみた。ちなみにこのスクリプトはTest.ps1というファイルに保存し、アセンブリが入っているbinフォルダに入れてある。

[System.Reflection.Assembly]::LoadFile((Join-Path (Get-Location) "\WatiN.Core.dll"))
$ie = New-Object WatiN.Core.IE
$ie.GoTo(http://www.devslife.com)

 

すると次のようなエラーが出る。

New-Object : Exception calling “.ctor” with “0” argument(s): “Could not load file or assembly ‘Interop.SHDocVw, Version
=1.1.0.0, Culture=neutral, PublicKeyToken=null’ or one of its dependencies. The system cannot find the file specified.”
At D:\Scripts\WatiN\bin\Test.ps1:2 char:17
+ $ie = New-Object <<<<  WatiN.Core.IE
    + CategoryInfo          : InvalidOperation: (:) [New-Object], MethodInvocationException
    + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand

 

なるほど。PowerShell.exeが入っているフォルダ内(C:\Windows\System32\WindowsPowerShell\v1.0)にInteropアセンブリが入っていないからなのだ。Interop.SHDocVw.dllをPowerShell.exeと同じフォルダ内にコピーするか、再コンパイルをして厳密名をアセンブリにつけるかだがとりあえずPowerShell.exeと同じフォルダにInteropアセンブリをコピーすることにする。PowerShellコンソールを再起動して再びスクリプトを実行すると・・・

今度は下のようなエラーが・・・

New-Object : Exception calling “.ctor” with “0” argument(s): “The CurrentThread needs to have it’s ApartmentState set t
o ApartmentState.STA to be able to automate Internet Explorer.”
At D:\Scripts\WatiN\bin\Test.ps1:2 char:17
+ $ie = New-Object <<<<  WatiN.Core.IE
    + CategoryInfo          : InvalidOperation: (:) [New-Object], MethodInvocationException
    + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand

て、手ごわすぎるぞ。でも上のエラーは完全にWatiNから意図的に投げ出されたものなのでちょっと希望の光が見えてきた。エラーによるとIEの自動化を行うにはスレッドがApartmentState.STAにセットされていなければいけないと。つまりPowerShellをApartment.STAにセットしなければいけないのか。そこでググってみるとPowerShellチームブログに到着。なんとカスタムアセンブリをこしらえてそれによってSTAにセットせよと。おっしゃあ、こうなったら意地でやってやる。

まずはPowerShellチームブログにあったカスタムCmdletをコンパイルしてそれをインストール。インストールの仕方はこのブログで一度紹介しているので参考にしていただきたい。インストールするとInvoke-Apartmentというコマンドレットが利用可能になる。それを使用してTest.ps1ファイル内のPowerShellスクリプトを実行するのだ。

Invoke-Apartment "STA" ([System.IO.File]::ReadAllText("Test.ps1"))

これを実行したらちゃんとIEが立ち上がってURLに行くことができたのだ。これでPowerShellからWatiNを使うことができる。

これは蛇足なのだが、上のコマンドでテキストファイルを読みだしているだけなのにどうしてGet-Contentコマンドを使わなかったというとGet-Contentが行ごとのObject配列を返してくるのでInvoke-Apartmentの二つ目のパラメータのString型と合わないために上のようにした。

あー苦労した。上のことを踏まえてなんとかもっと簡単にWatiNをPowerShellから使えるようにできないものだろうか。

これってどうしてもPowerShellでウェブアプリテストしたいという人には役立ちそうなので英語でも書いてみることにする。

PowerShellでウェブアプリUIテスト

と聞くと「おお!誰かがそんなフレームワークを書いたのか!?」と思われそうだが、そうではない。Watirのような完全なラッパー(Yoのラッパーじゃなくて、包むのほう)は自分が知る範囲では存在しない。というのはうそでこのブログを見直している間にそんなフレームワークを発見した。それは最後に述べるとして、IEはCOM経由で.NETから操ることができるのでWatirのようなフレームワークを作るのは時間がかかるかもしれないが、そんなに難しいことではないような気がする。(気がするだけ)前述のように.NETからIEをCOM経由で操ることができるのでPowerShellからもIEを操ることができるのだ。

$ie = New-Object -COM “InternetExplorer.Application”

をPowerShellで実行すると$ie変数の中にIEが格納される。それを使ってとりあえず何でも出来てしまうのだ。タスクマネジャーを見てみるとiexplorer.exeのプロセスが出現したのがわかる。では$ie変数に格納されたIEを少々操ってみることにしよう。

$ie.Navigate(“http://www.itmedia.co.jp“)
$ie.visible = $true

とするとIEが表れて指定したURLへ行ってくれる。

さらに・・・

$ie | gm

を実行すると$ieに格納されたIEオブジェクトのメンバを見ることができる。HTMLのそれぞれの要素への参照を得るには次のようにすると良い。

$txtNamae = $ie.document.getElementByID(“txtNamae”)
$txtNamae.value = “名無しのごんべい”

そうするとテキストフィールドに「名無しのごんべい」という値が入る。

このようにDOMを直接歩いて取り合えず何でも出来てしまいそうだ。ただ前述のとおりフレームワーク化されていないので、PowerShellを使ってすべて自分でUI用のユニットテストを作ろうとするとかなりガシガシ書かなければいけない部分って多そうである。

そういえばPowerShellチームブログで過去に「ユニットテストのPowerShell用のフレームワークって必要だと思う?」と一般の人々に聞いていたのを思い出す。あれってどうなったんだろう?ググってみるとこんなのもある。面白そう。あれれ?さらにさらに.NET用のWatirでWatiN(ワットイン)というのを発見。これだったらPowerShellで読み込んで出来そう。一番最近の更新が2月12日だから結構最近だ。試してみる価値あるかも。試してみたらまたこのブログで紹介します。

Cmdletの数

どうでもいいことかもしれませんが、PowerShellのV1では129個で、Windows7のPowerShellのV2ではget-command | where {$_CommandType -eq “Cmdlet”}として変数に入れてその結果の数を数えると、235個でした。今のところCmdletが106個も増えたことになります。どうでもいいけどちょっと気になる数字でした。

MSMQとPowerShell

この記事は以前にdevslife.comで紹介したものです。

かみさんも息子も床に就き、自分だけが起きているそんな静かな時間が好きだ。こんな時間に技術に関するブログを書くのがいい。

.NET FrameworkにはSystem.Messagingという名前空間があってその中にはMSMQと直接通信できるクラスが存在する。MSMQとは何か。以下マイクロソフトのサイトから抜粋

MSMQ (Microsoft Message Queuing) は、Microsoft WindowsR のコンポーネントの 1 つであり、高い信頼性を持つメッセージ機能を実現する最新テクノロジの成果です。MSMQ を使用すると、高性能な従来のキューイング インフラストラクチャと API で必要とされる信頼性が得られます。

なんともマーケティング用の文章なので初心者の方だったら頭の周りにクエスチョンマークが百個ぐらい出てくることだろう。もうちょっと分かりやすく砕いて言うと、異システム間のメッセージのやり取りを可能にするものと言っていいと思う。ってこれも分かりにくいか・・・MSMQの詳しいことについてはこちらで読んでもらうことにして・・・

とりあえずMSMQにメッセージを書き込むコードから。

[System.Reflection.Assembly]::LoadWithPartialName("System.Messaging")
$queuePath = "FormatName:Direct=OS:ServerName\private$\QueueName"
$mq = new-object System.Messaging.MessageQueue($queuePath)
$mq.Send("Hello World")

System.MessagingはデフォルトではPowerShellのメモリに読み込まれていないので一行目のような作業が必要になる。後は見てのとおり単にMessegeQueueクラスを使って文字通りメッセージを送るだけ。今回の例ではStringの型をSendに入れているがこれは他のオブジェクトでもなんら差し支えない。

では、MSMQからメッセージを受け取るにはどうしたらいいだろうか。これは実際にPowerShellでやってみて少々手こずったが、 次のようにすると読み込むことができる。

[System.Reflection.Assembly]::LoadWithPartialName("System.Messaging")
$mq = New-Object System.Messaging.MessageQueue("ServerName\private$\QueueName")
$formatter = new-object System.Messaging.XmlMessageFormatter([string])
$mq.Formatter = $formatter
$expiration = New-Object System.TimeSpan(0, 0, 10) #5 seconds
$msg = $mq.Receive($expiration)

$msg.Body

上のコードを少々説明することにしよう。

一行目は前の例と同じでSystem.MessagingのアセンブリをPowerShell上に読み込んで使えるようにするものだ。メッセージを読み込む場合にもMessageQueueクラスを使うのには変わりないが、そのほかに今度はformatterが必要になってくる。つまりこれは MSMQに存在するSerializeされたオブジェクトをDeserializeするために必要なのである。たとえそれがStringみたいな基本的な型であってもだ。

またメッセージを読み込む場合はExpiration、つまり読み込み時の期限切れの時間幅を決めておいたほうがいいだろう。それがないと Receiveメッソドを実行したときにMSMQが全くない場合は永遠に待ち続けることになる。でも待っている時間幅を決めておくと、例えば5秒以内に MSMQにメッセージが入ってこなかった場合はExceptionを投げてくれる。それをちゃんと捕まえて処理してやれば何の問題もないはずだ。

とたったの10行ぐらいで、簡単にPowerShellでMSMQへの読み書きができてしまうのだ。