◆VB 2005でのWindows Service制御 - 実装1

[ 上へ ]

・定数定義

前置きが長くなりましたがServiceControllerExの実装です。基本クラスの 継承の前に、System.ServiceProcess名前空間をインポートします。 ServiceControllerEx.vbの先頭にImports System.ServiceProcessを記述します。

そして、ServiceControllerを継承するためにServiceControllerExクラスの 一行目にInherits ServiceControllerをいれてください。

Imports  System.ServiceProcess    'サービス関係名前空間

Public Class ServiceControllerEx
    Inherits ServiceController

End Class

サービスをどの状態に変更したいのかを列挙定数として定義します。当然ですが、 サービスにはどのような状態があるのか分からないと定義できないです。 サービスの状態を取得するにはServiceController.Status()プロパティを使いますが、 この戻値がServiceControllerStatus列挙定数です。

実際どんな値があるのか見てみます。 ツールバーの表示(V)→オブジェクトブラウザ(J)からServiceControlStatus列挙定数を 選択すると画面右に定数値が表示されます。

これを見ると状態としては、Paused(一時停止)、Running(実行中)、 Stopped(停止)があることがわかります。ということは状態を変更するパターンとしては

Paused→Continue

Running→Paused

Running→Stopped

Stopped→Running

なので
Imports ステートメントの下あたりに以下のように列挙定数ServiceActionを定義してください。

Imports System.ServiceProcess     'サービス関係

'サービスステータス変更アクション
Public Enum ServiceAction
    Svc_Start = 1
    Svc_Stop = 2
    Svc_Pause = 3
    Svc_Continue = 4
End Enum

Public Class ServiceControllerEx

End Class

次に、サービスの状態はStatus()で取得しますが存在しないサービス名で ServiceControllerのインスタンスを作ってStatus()プロパティを実行すると例外が 発生するので、その対策用にあらためてServiceControlStatus列挙定数を定義します。 ServiceAction列挙定数の定義の下に

'サービスステータス
Public Enum ServiceControllerStatus
    Stopped = 1
    StartPending = 2
    StopPending = 3
    Running = 4
    ContinuePending = 5
    PausePending = 6
    Paused = 7
    None = 0     '例外発生対策用
End Enum

上記のように定義してください。None定数以外はSystem.ServiceProcess名前空間の ServiceConrollerStatusとまったく同じにしてください。

・コンストラクタの実装

最初にコンストラクタからです。基底クラスのServiceControllerは3つのコンストラクタがオーバー ロードされています。今回はサービスを制御する対象のコンピュータはローカルに限っているので、 サービス名だけを引数に指定する、
ServiceController.New(name As String)だけを使います。

Public Class ServiceControllerExとEnd Classの間に以下を実装してください。

'コンストラクタ
Public Sub New(ByVal name As String)
    MyBase.New(name)    '基底クラスのコンストラクタ
End Sub

・Statusプロパティの実装

各メンバの実装です。まずは現在のサービスの状態を取得するStatus()プロパティからです。 基本クラスのStatus()となんらかわりないので実装する必要はないのですが、例外に 対応するためにシャドウします。
コンストラクタに続いて以下を記述してください。

'現在のサービスステータス取得
Public Shadows ReadOnly Property Status() As ServiceControllerStatus
    Get
        Dim ret As ServiceControllerStatus

        Try
            ret = MyBase.Status()
        Catch ex As Exception
            Return ServiceControllerStatus.None
        End Try

        Return ret
    End Get
End Property

宣言部のShadowsキーワードは基底クラスのServiceController.Status() プロパティを再定義するために、ReadOnlyは読み取り専用であることを示します。 Shadowsと似たものにオーバーライドがありますが、違いについては別の機会に書きたいと思います。 基本的な実装部分はMyBase.Status()だけです。例外がスローされたときはCatchステートメントで 先に定義した ServiceControllerStatus.Noneが返されるようにしています。

・ChangeStatusメソッドの実装

サービスの状態を変更するには基底クラスのContinue()、Pause()、Start()、Stop()を実行することに なります。ですが、現在のサービスの状況を確認しないで各メソッドを実行すると例外がスローされる ことがあります。例えば、現在実行中のサービスに対してStart()メソッドを実行すると例外がスロー されます。実際は、こういうときは何もしないようにしたいので、現在のサービスの状態を確認して メソッドが実行されるような実装をしてみます。

'サービスステータスの変更
Public Function ChangeStatus(ByVal action As ServiceAction) As Boolean      '(A)
    Dim ret As Boolean = False

    Try
        Select Case Status()            '(B)
            Case ServiceControllerStatus.Paused         '一時停止
                If action.Equals(ServiceAction.Svc_Continue) Then           '(C)
                    Me.Continue()
                    WaitForStatus(ServiceControllerStatus.Running)
                    ret = True

                ElseIf action.Equals(ServiceAction.Svc_Stop) Then
                    Me.Stop()
                    WaitForStatus(ServiceControllerStatus.Stopped)
                    ret = True
                End If

            Case ServiceControllerStatus.Running        '実行中
                If action.Equals(ServiceAction.Svc_Pause) Then
                    If CanPauseAndContinue() Then
                        Me.Pause()
                        WaitForStatus(ServiceControllerStatus.Paused)
                        ret = True
                    End If

                ElseIf action.Equals(ServiceAction.Svc_Stop) Then
                    If CanStop() Then
                        Me.Stop()
                        WaitForStatus(ServiceControllerStatus.Stopped)
                        ret = True
                    End If
                End If

            Case ServiceControllerStatus.Stopped        '停止中
                If action.Equals(ServiceAction.Svc_Start) Then
                    Me.Start()
                    WaitForStatus(ServiceControllerStatus.Running)
                    ret = True
                End If

            Case ServiceControllerStatus.None           '例外用          '(D)
                ret = False

            Case Else
                ret = False
            End Select

    Catch ex As Exception
        ret = False
    End Try

    Return ret
End Function

(A)の宣言部で引数は、先に定義した変更しようとする状態を示す ServiceAction列挙定数を指定します。

戻値は、存在しないサービスのために現在の状態を取得できなかったりしたときは Falseを、メソッドが成功した時はTrueを返します。(現在の状態が実行中で、引数に Svc_Start を指定した場合など、変更しようとする状態と現在の状態が同じ場合はFalseを返します)

(B)のStatus() は派生クラスServiceControllerExでシャドウしたプロパティです。これで現在の サービスの状態を取得して、Select Caseステートメントで場合分けをしています。

例えば、現在の状態が停止中の時、上記コードの(C)のCaseが実行されます。 引数actionは変更しようとするサービス状態を指定しますが、EqualsメソッドでSvc_Continueと 同じかどうかを確認してそうであればMe.Continue()を実行してそれ以外は何もしません。

VBAの癖で If action = Svc_Continue Then としたくなるのですが、 Equalメソッドがあるということは VB2005以降はそれを使いなさいということなんでしょうか?

次のWaitForStatusメソッドは目的の状態になるまで(サービス状態の変更が完了するまで) 時間差があるので待機するメソッドです。より細かく制御するにはタイムアウトまでの時間を設定できる オーバーロード版のWaitForStatusを使いますができるだけ単純にするために、ここでは無期限に 待機するWaitForStatusメソッドを使用しました。

以下、現在の状態が実行中、停止中の場合、それぞれactionが Svc_Pause、Svc_StopとSvc_Runningに対して上記と同様の処理を行います。(D)でStatus()プロパティで 正しく現在の状態が取得できない時、ServiceControllerStatus.Noneを返しますがこの時はメソッドの 戻値をFalseに設定します。

以上でサービスの状態の取得/変更の実装はできました。基本的に基底クラスのServiceControllerの プロパティ/メソッドをラップした程度なのでそんなに難しい点はないと思います。

次回はスタートアップの種類の取得/変更の実装をします。 オブジェクトブラウザでServiceControllerの プロパティ/メソッドを見てもそれらしいのがないので自力で実装しないといけないので大変そうに 思われますが、ある方法でできるだけ楽をしようと思います。