Hey, Scripting Guy!

Scripting Guys が皆さんの質問にお答えします

Hey, Scripting Guy!

TechNet コラムへようこそ。このコラムでは、よく寄せられるシステム管理スクリプトに関する質問に Scripting Guys がお答えします。システム管理スクリプトについて質問がある場合は、scripter@microsoft.com (英語のみ) までお送りください。すべての質問に回答することはできないかもしれませんが、可能な限り対応いたします。

詳細情報

Hey, Scripting Guy! カテゴリ別アーカイブ

Hey, Scripting Guy! 日付別アーカイブ

Hey, Scripting Guy! ダウンロード

Spacer

*

あるプロセスを開始し、そのプロセスが終了した時点でユーザーをログオフさせる方法はありますか

Hey, Scripting Guy! Question

Scripting Guy さん、よろしくお願いします。あるプロセスを開始し、そのプロセスが終了するまで待機してから、ユーザーをコンピュータからログオフさせるスクリプトを作成する方法はありますか。

-- AG

SpacerHey, Scripting Guy! AnswerScript Center

AG さん、こんにちは。このコラムを執筆している Scripting Guy は、自慢できるような話のネタがあまりないというのが主な理由ですが、自慢話をするような人間ではありません。しかも、謙虚で腰の低いのです。2、3 週間前に自分がしたことについてお話していなかったのはそのためです。今日は他に話すことがないので (「なに、いつものスクリプトのコラムで、スクリプトについて話せないのか」とおっしゃいますか)、スポーツにおいて間違いなく有史以来最大の偉業についてお話すべきときなのかもしれません。

それはどういうことでしょう。ランス アームストロングの話でしょうか。確かに、ランス アームストロングは、いくつかの功績を残しました。しかし、はっきりさせましょう、ランス アームストロングがしたことは、自転車乗りに過ぎません。ランス アームストロングは、バスケット ボールをコートの端から反対側のバスケットに向かってボールを投げてシュートを決めたことがあるでしょうか。

おや、あるんですね。どうりで。でも、残念でした。ランス アームストロングが功績を自慢したいのなら、彼自身で日刊のスクリプト コラムを連載すればいいでしょう。

これは、Scripting Guy とその息子がいつものようにワンオンワンでバスケットボールの試合をしていたときのことです。このときは、残り時間わずかなところでスリーポイントラインの外からシュートを決めて、父親である Scripting Guy が辛くも勝利を収めました。(父親と対戦するときは常に優秀なスポーツマンである) 息子は、ボールを拾うと体育館の反対側にあるバスケット目がけてシュートを試みました。よくあることですが、こういうフラストレーションからくる行動は突如として競争に代わり、どちらが先に反対側のバスケットにシュートを決められるかを競うことになりました。

告白すると、Scripting Guy の最初のシュートは情けないことに、フリースローラインのあたりに落ちました (息子は大喜びしたことを付け加えておきます)。しかし、このばつの悪い思いは、Scripting Guy を奮い立たせました。15 本目のシュートだったでしょうか、ついに、シュートが決まりました。勝負あったな、息子よ。

念のためですが、Scripting Guy の楽しみは、息子を負かすことだけではありませんよ。

いずれにせよ、コートの反対側にあるバスケットにシュートを決めるのは、歴史上もっとも偉大なスポーツの偉業であることは、だれもが認めるに違いありません。いや、スポーツであるかどうかに関係なく、偉業なのかもしれません。歴史上、2 番目に大きな偉業を決めるのは難しいですね。しかし、控え目に考えても、あるプロセスを開始し、そのプロセスが終了するまで待機してから、ユーザーをコンピュータからログオフさせる次のスクリプトを、その候補として挙げたいと思います。

strComputer = "."

Set objWMIService = GetObject _
    ("winmgmts:\\" & strComputer & "\root\cimv2:Win32_Process")
 
objWMIService.Create "Notepad.exe", null, null, intProcessID

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colItems = objWMIService. _
    ExecNotificationQuery("Select * From __InstanceDeletionEvent " _ 
            & "Within 1 Where TargetInstance ISA 'Win32_Process'")
Do 
    Set objProcess = colItems.NextEvent
    If objProcess.TargetInstance.ProcessID = intProcessID Then
        Exit Do
    End If
Loop

Set objWMIService = GetObject("winmgmts:{(Shutdown)}\\" & _
        strComputer & "\root\cimv2")

Set colItems = objWMIService.ExecQuery _
    ("Select * from Win32_OperatingSystem")
 
For Each objItem in colItems
    objItem.Win32Shutdown(0)
Next

おっしゃるとおりです。これは、少し長いスクリプトですね。でも仕方ありません。とにかく、これがどのようなしくみになっているか、細かく見ていきましょう。

ご覧のとおり、まずローカル コンピュータの WMI サービスにバインドします。ここで注意すべき重要なことが 2 つあります。1 つは、Win32_Process クラスに直接バインドしていることです。これは、次に示す、このコマンドの \root\cimv2\:Win32_Process の部分が該当します。

Set objWMIService = GetObject _
    ("winmgmts:\\" & strComputer & "\root\cimv2:Win32_Process")

2 つ目は、WMI スクリプトを紹介するときは、いつもこんな風に言っています。「〜 (なんとかかんとか)。でも、もちろん、このスクリプトは、リモート コンピュータに対しても実行できます」と。今回も、これが当てはまるでしょうか。このスクリプトをリモート コンピュータに対して実行できるでしょうか。ええ、おそらくできるでしょう。実際、このスクリプトはリモート コンピュータに対して問題なく実行できるでしょう。ただし、作成するプロセスは、非表示のウィンドウで実行されます。つまり、そのプロセスは、リモート コンピュータ上で (それ以外の場所でも) 表示されません。アプリケーションが、ユーザーによる操作を必要としないで実行 (および終了) できる場合は、リモート コンピュータに対してこのスクリプトを問題なく実行できます。ただし、アプリケーションがなんらかのユーザーの操作を必要とする場合は、このスクリプトはリモート コンピュータに対して実行できません。それは、だれもこのアプリケーションを操作できないためです。

ちなみに、これはすべてセキュリティ上の理由によるもので、どうすることもできません。

WMI サービス (と Win32_Process クラス) に接続したら、次のコードを使用してアプリケーション (ここでは、Notepad.exe) を起動しています。

objWMIService.Create "Notepad.exe", null, null, intProcessID

ご覧のとおり、これは複雑極まりないというほどのものではありません。Create メソッドを呼び出し、その後に作成するプロセスの名前を指定しています。さらに、このプロセス名の後には 1 組の Null パラメータ (このスクリプトでは必要でない省略可能なパラメータ) があり、それに続いて、intProcessID という名前の "出力パラメータ" を指定しています。

この出力パラメータは、このスクリプトが機能するための鍵です。出力パラメータは、メソッドに渡す変数に過ぎません。メソッドは、この変数に値を代入します。Create メソッドの場合は、この値は、メモ帳 (Notepad.exe) の新しいインスタンスのプロセス ID になります。言うまでもないことですが、これにより、アプリケーションが終了した時点を特定できます。このプロセス ID が消えたら、アプリケーションが実行されていないことになります。

アプリケーションを起動したら、次はアプリケーションが終了するのを待機します。それには、WMI サービスにバインドし直し (今回は root\cimv2 名前空間にのみバインドします)、次のような新しい WMI クエリを作成します。

Set colItems = objWMIService. _
    ExecNotificationQuery("Select * From __InstanceDeletionEvent " _ 
            & "Within 1 Where TargetInstance ISA 'Win32_Process'")

ここでは、イベント サブスクリプションを作成しています。これにより、__InstanceDeletionEvent クラスの新しいインスタンスが作成されるたびに WMI から通知されるようにしています。名前からわかるように、このクラスの新しいインスタンスは、オブジェクトが削除されるたびに作成されます。

おっしゃるとおりです。少し誤解を招く説明をしていますね。"あらゆる" オブジェクトが削除されたときに通知されるようにしているのではありません。Where 句が示すように、該当するオブジェクト (TargetInstance) が Win32_Process クラスのメンバである場合にのみ通知されるようにしたいのです。つまり、プロセスが削除されたときにのみ (つまり、プロセスが終了されるたびに)、通知されるようにします。

: __InstanceDeletionEvent クラスやら、TargetInstance オブジェクトやら、ここで話していることがおわかりにならないというのですか。その場合は、WMI イベントについての Scripting Week 2 Webcast (英語) を参照してください。確かに、それでも何を話しているかわからないでしょう。このコラムは人々に対してそういう効果があるのです。しかし、少なくとも __InstanceDeletionEvent クラスや TargetInstance オブジェクトなどについての理解を深めることができます。

イベント サブスクリプションを作成したら、次のような永遠に実行される Do ループを作成します (終了条件がどこにも指定されていないことに注意してください)。

Do 
    Set objProcess = colItems.NextEvent
    If objProcess.TargetInstance.ProcessID = intProcessID Then
        Exit Do
    End If
Loop

いつものことながら、もちろん、おっしゃるとおりです。 永遠に続くものなどありませんね。やはり、このループも永遠に実行され続けるということはありません。ここでは、プロセスが削除されるまで待機します。プロセスが削除されると、イベント サブスクリプションがトリガされ、それを受けてスクリプトが次のコード (関連イベントが発生するまでスクリプトを "ブロック" するコード) から抜けます。

Set objProcess = colItems.NextEvent

イベントが発生すると、TargetInstance オブジェクトの ProcessID プロパティを確認して、この値が起動したアプリケーションの ProcessID を示す intProcessID 変数の値と等しいかどうかを判断します。

If objProcess.TargetInstance.ProcessID = intProcessID Then

この 2 つの ID が異なっているとします。問題ありません。終了されたプロセスが、このスクリプトで起動したプロセスではなかったというだけのことです。したがって、再びループ処理の先頭に戻り、次のプロセスの削除を待ちます。

一方、2 つのプロセス ID が同じだったとします。どの時点であってもプロセス ID は一意であるため、これはつまり、起動したプロセスが終了したことを意味します。したがって、今度はユーザーをコンピュータからログオフさせる必要があります。

そのためには、まず Exit Do ステートメントを呼び出して、前述の永続ループから抜けます。無事にループから抜けたら、次のコード ブロックを実行します。

Set objWMIService = GetObject("winmgmts:{(Shutdown)}\\" & _
        strComputer & "\root\cimv2")

Set colItems = objWMIService.ExecQuery _
    ("Select * from Win32_OperatingSystem")
 
For Each objItem in colItems
    objItem.Win32Shutdown(0)
Next

ここでは、また最初に WMI サービスにバイドしています。今回は、バインド文字列に Shutdown 特権が指定されています (確かに Shutdown 特権をもっと前の段階で指定し、このオブジェクト参照を単純に再利用することもできます。しかし、この方法の方が、説明をするうえでわかりやすいと考えたのです)。接続を確立したら、次のクエリを使用して、Win32_OperatingSystem クラスのすべてのインスタンスを取得します (このクラスでは、現在のオペレーティング システムについての情報しか取得されないため、常に結果のコレクションには 1 項目しか含まれません)。

Set colItems = objWMIService.ExecQuery _
    ("Select * from Win32_OperatingSystem")

次に、コレクション内の各項目をループ処理する For Each ループを作成します。項目ごとに Win32Shutdown メソッドを呼び出し、唯一のパラメータとして値 0 を渡します。

objItem.Win32Shutdown(0)

ご参考までに、この 0 は Win32Shutdown メソッドにコンピュータからユーザーをログオフするように指示する値です。Win32Shutdown メソッドに渡すことができる他の値、たとえば、コンピュータを再起動したり、完全にシャットダウンすることができる値はあるでしょうか。もちろんです。詳細については、「Microsoft Windows 2000 Scripting Guide」(英語) を参照してください。

AG さん、これで問題を解決できるはずです。

Scripting Guy 親子のその後に興味がある方のために付け加えると、Scripting Guy の息子はその後復讐を果たしました。父親の Scripting Guy と次にワンオンワンで対戦したとき、彼は父親に圧勝しました。しかし、少なくとも差し当たりは、Scripting Guy が最終的な勝利を手にしています。この試合の後で、2 人はスリーポイント シュート競争をしました。Scripting Guy の息子が最初にトライし、15 本のうち 7 本を決めました。父親の Scripting Guy がこの競争に勝つには、何本のシュートを決めればよいでしょうか。そう、8 本です。

確かに、皆さんのおっしゃるとおりです。Scripting Guy は仕事を辞めて、専業のバスケット ボール選手に転向するべきです。興味深いことに、これは、これまで数年にわたり上司から言われていることでもあります。


ページのトップへページのトップへ