◆VBA (VB6)でのWindows Service制御 - WMI編 その2

[ 上へ ]

・前準備

今回もExcel 2000 - Windows XPという環境で行います。何はともあれ、まずは WMI スクリプトライブラリを使えるように参照設定しないといけません。 新しいBookを作成してAlt + F11でVBエディタを起動します。起動したら、ツール(T)→参照設定(R)で 参照設定のダイアログを表示させます。

コンポーネントの中で Microsoft WMI Scripting V1.x Library を見つけてチェックをしてOKをクリックしてください。それではオブジェクトブラウザで 確認しながら実装します。

プログラムの流れとしては
1.WMI名前空間(root\cimv2)への接続
2.その名前空間内のWin32_ServiceクラスのインスタンスのCollectionを取得。
3.目的のインスタンス(サービス)への参照を取得。
4.サービスの状態、スタートアップの種類をあらわすState、StartModeの各プロパティを実行。
という流れになります。

・サービスの状態/スタートアップの種類の取得

今回もSQL Server 2005 Expressのサービス情報を取得することにします。 まず、前頁の CIM Studioを使ってSQL Server 2005 Expressの インスタンスを表示して、各プロパティを確認しておきましょう。 目的のインスタンスにたどり着くまでの過程をプログラミングしていくので 雰囲気をつかんでおいてください。

今回はサービスの状態の取得もスタートアップの種類の取得もほぼ同様のコードなので 引数で取得するプロパティの種類を選択するようにしました。 標準モジュールを追加してそこに以下を記述してください。

'サービスの状態/スタートアップの種類の取得
'引数 argsName : サービス名
'     argsPropType : 取得するプロパティ名(StateまたはStartMode)
'戻値 第二引数 - Stateの場合
'                Running , Stopped, Paused, None(エラー時)
'                StartModeの場合
'                Auto , Manual, Disabled, None(エラー時)
Public Function GetServiceValue(argsName As String, _
                                argsPropType As String) As String
    Dim voLocator   As WbemScripting.SWbemLocator
    Dim voServices  As WbemScripting.SWbemServices  '名前空間上のオペレーション
    Dim voInstances As WbemScripting.SWbemObjectSet 'サービスインスタンス群
    Dim voInstance  As WbemScripting.SWbemObject    'サービスインスタンス

    On Error Resume Next

    GetServiceValue = "None"

    'SWbemLocatorオブジェクト作成
    Set voLocator = CreateObject("WbemScripting.SWbemLocator")          '(A)
    If Err.Number <> 0 Then Exit Function

    'root\cimv2名前空間に接続
    Set voServices = voLocator.ConnectServer(strServer:=".", _
                                             strNamespace:="root\cimv2")    '(B)
    If Err.Number <> 0 Then Exit Function

    'Win32_Serviceクラスのインスタンス群の取得
    Set voInstances = voServices.InstancesOf("Win32_Service")   '(C)

    '各インスタンスから対象のサービスを取得
    For Each voInstance In voInstances
        If Err.Number <> 0 Then Exit Function

        '対象のサービスのプロパティの値を取得
        If voInstance.Properties_("Name") = argsName Then   '(D)
            GetServiceValue = voInstance.Properties_(argsPropType): Exit For
        End If
    Next voInstance

    Set voLocator = Nothing

End Function

(A)で名前空間への接続のためのSWbemLocatorを作成し、(B)でroot\cimv2名前空間に 接続します。第一引数の"."はローカルコンピュータを示します。
root\cimv2名前空間内のWin32_Serviceクラスのインスタンス群を(C)で取得します。 For EachステートメントでNameプロパティにより目的のインスタンスを判断しStateまたはStartMode プロパティの値を取得します。CIM StudioでNameプロパティを見ると鍵マークがついているのは このプロパティの値がキーという意味で(D)でインスタンスを特定できます。

Win32_Serviceのインスタンス数はそれほど多くないのでFor Eachで総当りしても それほど時間はかかりませんが、EventLogあたりになるとそうとうたいへんです。そこで目的の インスタンス名(サービス名)が分かっているのでしたら(C)の部分を以下のように変更して クエリを発行したほうがいいかもしれません。

Dim vsSQL   As String

vsSQL = "SELECT * FROM Win32_Service WHERE Name = '" & argsName & "'"
Set voInstances = voServices.ExecQuery(vsSQL)

あるいは、せっかくCIM Studioを使っているのでしたら目的のインスタンスの プロパティの下のほうにある __PATH で表示されている文字列を参考にして直接目的の インスタンスを取得する方法がベストかと思います。

'サービスの状態/スタートアップの種類の取得
'引数 argsName : サービス名
' argsPropType : 取得するプロパティ名(StateまたはStartMode)
'戻値 第二引数 - Stateの場合
'                Running , Stopped, Paused, None(エラー時)
'                StartModeの場合
'                Auto , Manual, Disabled, None(エラー時)
Public Function GetServiceValue(argsName As String, _
                                argsPropType As String) As String
    Dim voLocator   As WbemScripting.SWbemLocator
    Dim voServices  As WbemScripting.SWbemServices '名前空間上のオペレーション
    'Dim voInstances As WbemScripting.SWbemObjectSet 'サービスインスタンス群
    Dim voInstance  As WbemScripting.SWbemObject    'サービスインスタンス
    Dim vsPath      As String

    On Error Resume Next

    GetServiceValue = "None"

    'SWbemLocatorオブジェクト作成
    Set voLocator = CreateObject("WbemScripting.SWbemLocator")
    If Err.Number <> 0 Then Exit Function

    'root\cimv2名前空間に接続
    Set voServices = voLocator.ConnectServer(strServer:=".", _
                                             strNamespace:="root\cimv2")
    If Err.Number <> 0 Then Exit Function

    '目的のインスタンスへのPath
    vsPath = "\\" & Environ("COMPUTERNAME") _
           & "\root\cimv2:Win32_Service.Name='" & argsName & "'"
Set voInstance = voServices.Get(vsPath) If Err.Number <> 0 Then Exit Function GetServiceValue = voInstance.Properties_(argsPropType) Set voLocator = Nothing End Function

VBエディタからCtrl + Gでイミディエイトウィンドウから実行してみます。

・サービスの状態の変更

次にサービスの状態とスタートアップの種類の変更はWMIではどうやるかということですが、 これもCIM Studioが助けてくれます。まず、 ここの手順でSQL Server 2005 Expressの インスタンスを右ペインに表示させます。その上部にMethodsタブがあるのでそれをクリックすると, インスタンスが実行できるメソッド名を確認できます。それをみるとPauseService, ResumeService, StartService, StopServiceがサービスの状態を変更するメソッドだとわかると思います。

前項で分かるように WMI スクリプトライブラリではサービスのインスタンスへの参照はSWbemObject に代入されます。今回はたまたまサービスを扱ってますが、SWbemObjectはさまざまなクラスの インスタンスへの参照が格納されます。これらのクラスの違う固有のメソッドを実行するために SWbemObjectにはExecMethodがあるのでこれを使います。

サービスの状態を変更してから要求した状態になるまでのタイムラグのために以前にも 使ったAPIのSleep関数を使うので宣言部に以下を記述してください。

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

以下が実装です。

'サービス状態変更
'引数 argsName  : サービス名
'     argsState : 変更する状態(Run, Continue, Pause, Stop)のいずれか
'戻値 成功 True, 失敗 False
Public Function ChangeServiceStatus(argsName As String, argsState) As Boolean
    Dim voLocator  As WbemScripting.SWbemLocator
    Dim voServices As WbemScripting.SWbemServices
    Dim voInstance As WbemScripting.SWbemObject
    Dim vsPath     As String
    Dim voRet      As WbemScripting.SWbemObject     'リターンオブジェクト

    On Error Resume Next

    'SWbemLocatorオブジェクト作成
    Set voLocator = CreateObject("WbemScripting.SWbemLocator")
    If Err.Number <> 0 Then Exit Function

    'root\cimv2名前空間に接続
    Set voServices = voLocator.ConnectServer(strServer:=".", _
                                             strNamespace:="root\cimv2")
    If Err.Number <> 0 Then Exit Function

    vsPath = "\\" & Environ("COMPUTERNAME") _
           & "\root\cimv2:Win32_Service.Name='" & argsName & "'"
    Set voInstance = voServices.Get(vsPath)
    If Err.Number <> 0 Then Exit Function

    'サービスの状態によって分岐
    Select Case voInstance.Properties_("State")
    Case "Stopped" '現在停止中   
        If argsState = "Run" Then '実行要求   
            Set voRet = voInstance.ExecMethod_("StartService")    '(A)
            If voRet.Properties_("ReturnValue").Value <> 0 Then Exit Function

            Do Until GetServiceValue(argsName, "State") = "Running"
                Sleep voInstance.Properties_("WaitHint")
            Loop

            ChangeServiceStatus = True
        End If

    Case "Paused" '現在一時停止
        If argsState = "Continue" Then '再開要求
            Set voRet = voInstance.ExecMethod_("ResumeService")
            If voRet.Properties_("ReturnValue").Value <> 0 Then Exit Function

            Do Until GetServiceValue(argsName, "State") = "Running"
                Sleep voInstance.Properties_("WaitHint")
            Loop
            ChangeServiceStatus = True

        ElseIf argsState = "Stop" Then '停止要求
            Set voRet = voInstance.ExecMethod_("StopService")
            If voRet.Properties_("ReturnValue").Value <> 0 Then Exit Function

            Do Until GetServiceValue(argsName, "State") = "Stopped"
                Sleep voInstance.Properties_("WaitHint")
            Loop
            ChangeServiceStatus = True

        End If

    Case "Running" '現在実行中
        If argsState = "Pause" Then '一時停止要求
            Set voRet = voInstance.ExecMethod_("PauseService")
            If voRet.Properties_("ReturnValue").Value <> 0 Then Exit Function

            Do Until GetServiceValue(argsName, "State") = "Paused"
                Sleep voInstance.Properties_("WaitHint")
            Loop
            ChangeServiceStatus = True

        ElseIf argsState = "Stop" Then '停止要求
            Set voRet = voInstance.ExecMethod_("StopService")
            If voRet.Properties_("ReturnValue").Value <> 0 Then Exit Function

            Do Until GetServiceValue(argsName, "State") = "Stopped"
                Sleep voInstance.Properties_("WaitHint")
            Loop
            ChangeServiceStatus = True
        End If

    Case Else

    End Select
End Function

注目点は(A)のExcecMethodです。CIM Studioで確認したWin32_Service固有の メソッドを実行しています。戻ってきたオブジェクト(voRet)にはReturnValueプロパティがあり、 ExecMethodの実行結果の如何を判断できます。

・スタートアップの種類の変更

最後のスタートアップの種類の変更ですが、これもCIM Studioで目的の サービスインスタンスのメソッドタブを見てみると、「ChangeStartMode」があります。 これを使うことになりますが、サービスの状態の変更のときと違い、メソッドが1つしかないので どの種類に変更するのかは引数で指定しなければならないのだろうという想像はつきます。 では、メソッド名だけを指定するExcecMethodでどうやって引数を設定するのかというと、 第二引数に引数の値を保持したSWbemObjectを指定するようになっています。

どんな引数を指定しなければいけないということは再びCIM Studioで確認できます。 同様にCIM Studioのメソッドタブで、ChangeStartModeを右クリックすると 「Edit Method Parameters」という項目があるので選択します。すると、 StartModeという引数名とデフォルトの値があるので確認しておきます。では実装します。

'スタートアップの種類の変更
'引数 argsName :    サービス名
'     argsStartType 変更するスタートアップの種類
'     (Automatic Manual Disabledのいずれか)
'戻値 成功 True 失敗 False
Public Function ChangeStartMode(argsName As String, _
                                argsStartType As String) As Boolean
    Dim voLocator   As WbemScripting.SWbemLocator
    Dim voServices  As WbemScripting.SWbemServices
    Dim voInstance  As WbemScripting.SWbemObject       'サービスインスタンス
    Dim vsPath      As String
    Dim voParams    As WbemScripting.SWbemObject       'メソッドのパラメータ
    Dim voRet       As WbemScripting.SWbemObject       'メソッド実行の戻値

    On Error Resume Next

    Set voLocator = CreateObject("WbemScripting.SWbemLocator")
    If Err.Number <> 0 Then Exit Function

    Set voServices = voLocator.ConnectServer(strServer:=".", _
                                             strNamespace:="root\cimv2")
    If Err.Number <> 0 Then Exit Function

    vsPath = "\\" & Environ("COMPUTERNAME") _
           & "\root\cimv2:Win32_Service.Name='" & argsName & "'"
    Set voInstance = voServices.Get(vsPath)
    If Err.Number <> 0 Then Exit Function

    'Win32_ServiceクラスのChangeStartModeメソッドのパラメータオブジェクト
    Set voParams = voInstance.Methods_("ChangeStartMode").InParameters      '(A)
    voParams.Properties_("StartMode").Value = argsStartType

    'パラメータを設定してメソッド実行
    Set voRet = voInstance.ExecMethod_("ChangeStartMode", voParams)         '(B)
    If voRet.Properties_("ReturnValue") <> 0 Then Exit Function

    ChangeStartMode = True
End Function

サービスインスタンスのメソッドの引数に指定する値は、(A)で示されるように、 CIM Studioで確認したメソッド名でSWbemMethodオブジェクト (voInstance.Methods_("ChangeStartMode"))のInParametersプロパティから パラメータに指定するSWbemObjectを取得して、同じくCIM Studioで確認したStartModeという 引数名で対応する値を設定します。最後に(B)でChangeStartModeを実行します。

・最後に

今回はWMIの利用例をWindowsサービスについてのみ書きましたが、 CIM Studioのクラス群をみてみればWindowsシステムの相当な情報の取得や変更ができる たいへん便利な機能だとわかると思います。個人的には、少々重いという欠点はあるものの それを差し引いてもあまりある便利さだと思います。

WMIを利用するには WMI スクリプトライブラリを利用する機会が多いとす思いますが、今のところ レファレンスが英語版しか確認してせん。ですが、この便利さを享受するために 理解されることをお勧めします。

次はいよいよ最後になりますが、VC++でのWindows サービスの制御について書きたいと思います。

参考文献
Windows Management Instrumentation(英語)
http://msdn.microsoft.com/en-us/library/aa394582(VS.85).aspx
WMI スクリプト入門 : 第 1 部
http://msdn.microsoft.com/ja-jp/library/ms974579.aspx
WMI スクリプト入門 : 第 2 部
http://msdn.microsoft.com/ja-jp/library/ms974592.aspx
WMI スクリプト入門 : 第 3 部
http://msdn.microsoft.com/ja-jp/library/ms974547.aspx
Windows Management Instrumentationの秘密
http://www.microsoft.com/japan/technet/scriptcenter/resources/wmifaq.mspx