第9回 WshControllerオブジェクトの詳細:Windows管理者のためのWindows Script Host入門(3/3 ページ)
WSHでは、リモート・コンピュータにスクリプトを送り込んで実行できる。これにより、複数のコンピュータの集中管理などが可能になる。
単純なリモート・スクリプトの例
それでは実際にWshControllerオブジェクトを利用したリモート・スクリプト実行の具体的な方法を解説しよう。最も単純なコードは次のようなものだ。これを例にとって説明する。この例では、「PC01」という名前のコンピュータ上で「test.vbs」というスクリプトを実行する。
01: strComputer = "PC01"
02: strScript = " test.vbs"
03:
04: set objController = WScript.CreateObject("WshController")
05: set objRemote = objController.CreateScript(strScript, strComputer)
06: WScript.Echo "リモートでスクリプトを実行します。"
07: objRemote.Execute
08: Do Until objRemote.Status = 2
09: WScript.Sleep 100
10: Loop
11: WScript.Echo "リモートでの実行は終了しました。"
リモートでスクリプトを実行するには、まずWshControllerオブジェクトを作成し(4行目)、次に実行したいスクリプト名でCreateScriptメソッドを実行する(5行目)。CreateScriptメソッドを実行すると、WshRemoteオブジェクトが作成され、オブジェクトが戻り値として返される。次に、作成されたWshRemoteオブジェクトのExecuteメソッドを実行することで、リモート・コンピュータ上でのスクリプト実行を開始させることができる(7行目)。このとき実行するスクリプト・ファイルは、実行対象のコンピュータ上のファイルではなく、操作を行うコンソール側のファイルを指定する(もちろん、UNCを利用してネットワーク上のファイルを指定してもよい)。つまり、リモートでスクリプトを実行するといっても、あらかじめスクリプト・ファイルを配布しておく必要はない。パスを指定しなければ、カレント・ディレクトリにあるスクリプトが利用される(カレント・ディレクトリの設定に関しては関連記事を参照)。
Executeメソッドは、リモートでのスクリプト実行を開始するが、その終了を待つわけではない。リモート先で実行されたスクリプトが終了するかどうかとは無関係に、Executeメソッドの実行はすぐに完了し、コンソール側のスクリプトはそのまま次の行(8行目以降)を実行する。
8〜10行目にあるのは、リモート・コンピュータでのスクリプト実行が完了するのを待つためのループである。リモート・コンピュータでのスクリプト実行が開始しないうちに、コンソール側スクリプトが終了してしまうと、リモートのスクリプトが正しく起動されない場合もあるので、通常はこのようなループを使ってリモート・スクリプトが終了するまで待機する。
8行目にあるとおり、リモート・スクリプトが終了したかどうかは、WshRemoteオブジェクトのStatusプロパティを確認することで判断できる。Statusプロパティは読み取り専用のプロパティであり、値としては0、1、2のどれかを取る。それぞれの戻り値の意味は次の表のとおりである。
値 | 意味 |
---|---|
0 | スクリプトはまだ実行されていない |
1 | スクリプトは実行中である |
2 | スクリプトの実行は終了した |
CreateScriptメソッドでオブジェクトが作成された時点ではStatusプロパティは0であり、Executeメソッドが実行されると1となり、スクリプトが終了すると2となる。スクリプトの終了を待つ場合には、前出のサンプル・スクリプトのように、Statusプロパティが2になるまでループを行えばよい。
リモートで実行されるスクリプトは、ディスプレイ(リモート・コンピュータのディスプレイ)には何も表示せずにバッググラウンドで動作する。メッセージ・ボックスを表示したり、標準入力を読み込んだりするプログラムを実行した場合でも、すべてバックグラウンドで処理される。従ってユーザーからの入力や、OKボタンのクリックなどはスクリプトでは受け取れず、処理できない。従ってこれらを必要とするスクリプトは、入力待ちのままデッドロックしてしまうので注意が必要だ。この場合、上記のサンプル・コードでは、リモート・スクリプトは正常終了しないので、いつまでもDoループから脱出できず、無限ループになってしまう。リモート・コンピュータで実行するスクリプトは、ユーザー入力などを必要としないようにする。なお、スクリプトのデバッグなどで、入力待ちや無限ループによりWSHスクリプトのプロセスを終了できなくなった場合は、タスクマネージャで「wscript.exe」のプロセスを終了することで、強制終了させることができる。
コンソール側コンピュータでは、test.vbsの実行を画面では確認できないし、リモート・コンピュータから実行結果を返すこともできないので、test.vbsでは実行結果をファイルに書き込むことにする。ファイルの処理には、FileSystemObject(FSO)というオブジェクトを使う。FSOの具体的な解説は次回行う予定である。今回の例では、以下のコードでD:\WSH\test.txtというファイルに現在時刻を書き込むことにした。
Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile("D:\WSH\test.txt", 8, True)
objFile.WriteLine Now
objFile.Close
以上の2つのスクリプトを準備し、実行することで、コンピュータ“PC01”上でtest.vbsを実行できる。test.vbsの処理が正しく終了したかどうかを知るには、対象マシンのD:\WSH\test.txtが更新されたかどうかを確認すればよい。
なお、Statusプロパティが2になるまでループして終了を待つと、リモート・スクリプトが何らかの理由で暴走したときに、コンソール側のスクリプトも無限ループになってしまう。これを避けたければ、WshRemoteオブジェクトのTerminateメソッドを使用することで、リモート・スクリプトを強制的に終了させることもできる。しかし、処理中にプロセスを終了すると処理途中のデータが破損する可能性があるので、例外的な場合以外にはTerminateメソッドはできるだけ使わずにリモート・スクリプト自身で終了するようにしておくべきだろう。
リモート・コードを強制終了させたければ、次のようにする。
01: strComputer = "PC01"
02: strScript = " test.vbs"
03:
04: set objController = WScript.CreateObject("WshController")
05: set objRemote = objController.CreateScript(strScript, strComputer)
06: WScript.Echo "リモートでスクリプトを実行します。"
07: objRemote.Execute
08: WScript.Sleep 1000
09: If objRemote.Status <> 2 Then
10: objRemote.Terminate
11: WScript.Echo "リモートでの実行を強制終了しました。"
12: Else
13: WScript.Echo "リモートでの実行は終了しました。"
14: End If
このスクリプトでは、実行開始から1秒間待った後(8行目)、リモートのスクリプトが終了したかどうかを判定し(9行目)、終了していなければ強制終了を行う(10行目)。
イベント・ハンドラを利用する
前出のサンプル・スクリプトでは、WshRemoteオブジェクトのStatusプロパティの値を用いて、リモート・スクリプトの状態によって処理を切り替えていた。これ以外としては、リモート・スクリプトの状態によって通知されるイベントを用いる方法がある。イベントにはStart、End、Errorの3種類があり、それぞれスクリプトの起動、終了、エラー発生を検出し、イベントごとに必要な処理を行えるようになっている。
イベントを利用するには、WScriptオブジェクトのConnectObjectメソッドを利用し、WshRemoteオブジェクトに対してイベント・ハンドラとなる関数のプレフィックスを対応付ける。イベント・ハンドラとは、イベントが発生したときに呼び出され、必要な処理を実行する関数である。
例えば、“Remote_”というプレフィックスを登録すると、リモート・スクリプトが起動するとRemote_Start関数が、終了するとRemote_End関数が、エラー発生時にはRemote_Error関数がそれぞれ実行されるようになる。関数名の後半はイベント名がそのまま使われるため、変更することはできない。これらの関数はSubステートメントで定義しておく。なお、Endイベントはエラー終了時にも発生するので、エラー時にはRemote_Error関数が実行された直後にRemote_End関数が実行されることになる。以下に例を示す。
01: strComputer = "PC01"
02: strScript = "test.vbs"
03:
04: set objController = WScript.CreateObject("WshController")
05: set objRemote = objController.CreateScript(strScript, strComputer)
06: WScript.ConnectObject objRemote, "Remote_"
07: objRemote.Execute
08: Do Until objRemote.Status = 2
09: WScript.Sleep 100
10: Loop
11:
12: Sub Remote_Start
13: WScript.Echo "リモートでスクリプトの実行を開始しました。"
14: End Sub
15:
16: Sub Remote_Error
17: WScript.Echo "リモートでスクリプトのエラーが発生しました。"
18: End Sub
19:
20: Sub Remote_End
21: WScript.Echo "リモートでの実行は終了しました。"
22: End Sub
6行目で、objRemoteオブジェクトで発生するイベントに対し、“Remote_”で始まる関数を対応付けている。イベント・ハンドラは12行目である。この例ではRemote_Start、Remote_Error、Remote_Endのすべてを定義しているが、必ずしもこの3つすべてを作成する必要はない。対応する関数がなければ、イベント発生時にも何も起こらずに処理が続けられる。
エラー情報の取得
リモート・スクリプトがエラーを発生したときには、WshRemoteオブジェクトのErrorプロパティ利用することで、エラー情報を取得することができる。Errorプロパティの内容はWshRemoteErrorオブジェクトであり、このオブジェクトに含まれるプロパティを見ることでスクリプト中のどこでどのようなエラーが生じたのかを確認できる。例えば、簡単なエラーメッセージはDescriptionプロパティにセットされるので、objRemoteオブジェクトで起こったエラーのエラーメッセージは“objRemote.Error.Description”で取得することができる。
WshRemoteErrorオブジェクトに含まれるプロパティには以下の6種類がある。
プロパティ | 値の内容 |
---|---|
Description | エラーメッセージ |
Line | エラーが発生したスクリプト中の行数 |
Character | エラーが発生した場所の行頭からの文字数 |
Source | COMオブジェクトでエラーが生じた場合、そのCOMオブジェクトの名前 |
SourceText | エラーの発生した行の内容 |
Number | エラーコード |
手元で試した限り、Sourceプロパティの内容はスクリプト中で使っているCOMオブジェクトとは関係なく、それがVBScriptのコンパイル時エラーなら「Microsoft VBScript コンパイル エラー」に、VBScript実行時のエラーから「Microsoft VBScript 実行時エラー」になるようである。エラー発生時に6種類すべての情報を出力するには次のようなスクリプトを書けばよい。
01: strComputer = "PC01"
02: strScript = "test.vbs"
03:
04: set objController = WScript.CreateObject("WshController")
05: set objRemote = objController.CreateScript(strScript, strComputer)
06: Wscript.ConnectObject objRemote, "Remote_"
07: WScript.Echo "リモートでスクリプトを実行します。"
08: objRemote.Execute
09: Do Until objRemote.Status = 2
10: WScript.Sleep 100
11: Loop
12: WScript.Echo "リモートでの実行は終了しました。"
13:
14: Sub Remote_Error
15: WScript.Echo "リモートでスクリプトのエラーが発生しました。"
16: WScript.Echo "行 :" & objRemote.Error.Line
17: WScript.Echo "文字 :" & objRemote.Error.Character
18: WScript.Echo "エラー :" & objRemote.Error.Description
19: WScript.Echo "コード :" & objRemote.Error.Number
20: WScript.Echo "ソース :" & objRemote.Error.Source
21: WScript.Echo "テキスト:" & objRemote.Error.SourceText
22: WScript.Quit
23: End Sub
このコードでは、エラー情報を出力した後にWScript.Quitでスクリプトの実行を終了している(22行目)。試しに、test.vbsでファイルを開く際にエラーが発生するように、存在しないパスを設定してみよう。
Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile("D:\WSH\dummy\test.txt", 8, True)
objFile.WriteLine Now
objFile.Close
これで実行すると、以下のようにエラー情報が出力される。
リモートでスクリプトを実行します。
リモートでスクリプトのエラーが発生しました。
行 :2
文字 :1
エラー :パスが見つかりません。
コード :-2146828212
ソース :Microsoft VBScript 実行時エラー
テキスト:
bjFile.WriteLine Now
この出力を見ると、2行1列から始まるSet文の処理中に、「パスが見つかりません」というエラーが発生したことが分かる。Sourceは「Microsoft VBScript 実行時エラー」となった。しかし、SourceTextプロパティは改行から始まって次の行が抽出されてしまっている。これはあまり正確な情報であるとはいえないようだ。
以上でWindows Script Host標準オブジェクト・モデルの解説は終わりである。次回は、標準オブジェクトではないが、ファイル入出力用途としてWSH中で頻繁に使われるFileSystemObjectオブジェクトについて解説する予定である。
Copyright© Digital Advantage Corp. All Rights Reserved.