デベロッパー

デベロッパー

VB.NetでWindowsサービスをリモートから制御/インストール/アンインストールする

aneezah
2009年1月13日 / 10:00
 
 

Windowsサービスの制御

 Windowsサービスにはいくつかの基本的な特性があります。まず、Windowsサービスは特定ユーザーの下で実行するように設定できます。また、Windowsサービスは親あるいは子サービスを持つことができます。親サービスとは、そのサービスが依存するサービスのことで、子サービスとは、そのサービスに依存するサービスのことです。この関係は、サービスの起動/停止の際に重要な意味を持ちます。サービスを正しく起動させるには、そのサービスの親サービスが起動していることを確認する必要があります。サービスを正しく停止させるには、そのサービスの子サービスが停止していることを確認する必要があります。

サービスの起動

 それでは、実際のコードを見てみましょう。VB.Netでサービスを制御するには、サービスプロセスの名前空間をインポートする必要があります。よって、最初の行は次のようになります。

Imports System.ServiceProcess
 まず、親サービスとの依存関係を何も確認せずにサービスを起動するコードを考えてみましょう。

Dim objWinServ As New ServiceController
objWinServ.ServiceName = sServiceName
objWinServ.MachineName = MachineName
StartService(objWinServ)

Private Sub StartService(ByVal Service As ServiceController)
   If Service.Status = ServiceControllerStatus.Stopped Then
      Try
         Service.Start()
         Service.WaitForStatus(ServiceControllerStatus.Running, _
            System.TimeSpan.FromSeconds(20))
      Catch ex As TimeoutException
         Status = "Could not Start " & Service.DisplayName & _
                  " - TimeOut expired"
      Catch e As Exception
         Status = "Could not Start " & Service.DisplayName & _
                  " - " & e.Message
      End Try

   End If

End Sub
 このコードはごく単純なもので、次のことを行っています。

  1. objWinServという名称の新規サービスコントローラを作成します。
  2. サービス名とマシン名を割り当てます(リモートで呼び出しを行うため)。
  3. objWinServオブジェクトをサービス起動ルーチンに提供します。
  4. 起動ルーチンは最初に目的のサービスのステータスをチェックし、サービスが停止しているかを確認します。同じ要領で、一時停止状態や実行中などの確認もできます。
  5. その後、起動ルーチンはサービスの起動を試みます。この例では、起動の最大待ち時間を20秒に設定しました。サービスが起動しない場合は、タイムアウト例外がスローされます。その他の一般的な例外についての処理も用意してあります。
 ここで、コードの確実性をより高めるために次の2点をチェックするようにしたいと思います。

  1. 事前に起動している必要がある親サービス
  2. サービスがサーバ上に存在しているか
 起動しておく必要がある親サービスを確認するには、以下の再帰プロシージャを使用します。

Private Sub CheckForParentServices _
   (ByVal Service As ServiceController)
      Dim objParentService As ServiceController
      For Each objParentService In Service.ServicesDependedOn
         CheckForParentServices(objParentService)
      Next
      Call StartService(Service)
End Sub
 objWinServオブジェクトをこのプロシージャに渡せば、後は再帰的に処理が行われます。

 サービスが存在するかチェックするには、サービスの有無に応じてブール値を返す、以下の関数を使用します。

Public Function CheckforService(ByVal ServerName As String, _
   ByVal ServiceName As String) As Boolean
      Dim Exist As Boolean = False
      Dim objWinServ As New ServiceController
      Dim ServiceStatus As ServiceControllerStatus
      objWinServ.ServiceName = ServiceName
      objWinServ.MachineName = ServerName

      Try
         ServiceStatus = objWinServ.Status
         Exist = True
      Catch ex As Exception
      Finally
         objWinServ = Nothing
      End Try
      Return Exist
End Function

サービスの停止

 次はサービスの停止について考えてみましょう。まずは基本の関数です。

Private Sub StopService(ByVal Service As ServiceController)
   Dim status As String
   If Service.Status = ServiceControllerStatus.Running Then
      Try
         Service.Stop()
         Service.WaitForStatus(ServiceControllerStatus.Stopped, _
                               System.TimeSpan.FromSeconds(20))
      Catch ex As TimeoutException
         status = "Could not Stop " & Service.DisplayName & _
                  "Service - TimeOut expired"
      Catch e As Exception
         status = "Could not Stop " & Service.DisplayName & _
                 "Service - " & e.Message
      End Try
   End If

End Sub
 objWinServには、起動の場合と同様の例外処理と待ち時間を設定しています。

 先ほどと同じ関数を使用して、サービスが存在するか事前にチェックできます。ただし、サービスの停止時には、そのサービスが停止すべき子サービスを持っているかを確認する必要があります。この処理を行うプロシージャは次のとおりです。

Private Sub CheckForChildServices(ByVal Service As ServiceController, _
                                  ByVal NextService As String)

   Dim objChildService As ServiceController

   For Each objChildService In Service.DependentServices
      CheckForChildServices(objChildService, NextService)
   Next

   If NextService = "Stop" Then
      Call StopService(Service)
   Else
      Call ContinueService(Service)
   End If

End Sub
 このプロシージャには、2つ目の属性NextServiceを追加しました。この属性は、子サービスのチェックルーチンを再利用しやすくするためのものです。ステータスを継続に変更したい場合も、子サービスが存在するかを確認する必要があります。そこで、ステータスとしてStopまたはContinueの値を取る属性情報を追加します。

 次に示すのは、サービスを継続させるためのプロシージャです。一時停止させるプロシージャも同様に記述できます。

Private Sub ContinueService(ByVal Service As ServiceController)
   Dim status As String
   If Service.Status = ServiceControllerStatus.Paused Then
      Try
         Service.Continue()
         Service.WaitForStatus(ServiceControllerStatus.Running, _
                               System.TimeSpan.FromSeconds(20))
      Catch ex As TimeoutException
         status = "Could not change status from Paused To _
                   Continue for " & Service.DisplayName " _
                  " - TimeOut expired"
      Catch e As Exception
         status = "Could not change status from Paused To _
                   Continue for " & Service.DisplayName & " - " _
                   & e.Message
      End Try
   End If

   If Service.Status = ServiceControllerStatus.Stopped Then
         Call StopService(Service)
   End If

End Sub
 各ステータスのプロシージャを呼び出す汎用関数を次に示します。

Public Function ControlServices(ByVal sServiceName As String, _
   ByVal sTask As String, ByVal MachineName As String)
   Status = ""
   Dim objWinServ As New ServiceController
   objWinServ.ServiceName = sServiceName
   objWinServ.MachineName = MachineName

      Select Case sTask

      Case "Reset"
         If objWinServ.Status = ServiceControllerStatus.Running Then
            'Service is currently running, so you will have to
            'stop it before starting it
            'First stop all it's child services, then stop the
            'service itself
            Call CheckForChildServices(objWinServ, "Stop")
         End If

         If objWinServ.Status = ServiceControllerStatus.Stopped Then
            'This is satisfied if the service was running and then
            'was stopped by code or if it was already stopped.
            'Service is already stopped, so just start it...
            'so first start all it's parent services
            Call CheckForParentServices(objWinServ)
            If Status = "" Then Status = "Successfully Started"
            Else
               Status = Status '"Could not Start " & _
                  objWinServ.DisplayName
            End If
         End If

      Case "Start"
         If objWinServ.Status = ServiceControllerStatus.Stopped Then
            'This is satisfied if the service was running and then
            'was stopped by code or if it was already stopped
               Call CheckForParentServices(objWinServ)
               If Status = "" Then
                  Status = "Could not Start " & _
                           objWinServ.DisplayName
               Else
                  Status = "Successfully Started"
               End If

         End If

      Case "Stop"
         If objWinServ.Status = ServiceControllerStatus.Running Then
               Call CheckForChildServices(objWinServ, "Stop")

               Status = "Successfully Stopped"

         ElseIf objWinServ.Status = ServiceControllerStatus.Stopped Then
               Status = "Successfully Stopped"
         Else
               Status = "Could not Stop " & objWinServ.DisplayName
         End If
   End Select
         Return Status
End Function
 これで、リモートからのサービス制御に必要なコードはほぼ揃いました。単にステータスを調べたい場合は、次のように記述することもできます。

Public Function GetServiceStatus(ByVal sServername As String, _
   ByVal sServiceName As String) As String

   Dim ServiceStatus As New ServiceController
   ServiceStatus.ServiceName = sServiceName
   ServiceStatus.MachineName = sServername

   Try
      If ServiceStatus.Status = ServiceControllerStatus.Running Then
         Return "Running"
      ElseIf ServiceStatus.Status = _
         ServiceControllerStatus.Stopped Then
         Return "Stopped"
      Else
         Return "Intermidiate"
      End If
   Catch ex As Exception
      Return "Stopped"
   Finally
      ServiceStatus = Nothing
   End Try

End Function

Windowsサービスのインストールとアンインストール

 次に、インストールとアンインストールについて考えてみます。最初のステップで、下記のとおりグローバル宣言を行います。

Private Declare Ansi Function WritePrivateProfileString Lib _
   "KERNEL32.DLL" Alias "WritePrivateProfileStringA" _
   (ByVal lpSectionName As String, ByVal lpKeyName As String, _
    ByVal lpKeyValue As String, ByVal lpFileName As String) As Integer
   Private Declare Ansi Function GetPrivateProfileString Lib _
      "KERNEL32.DLL" Alias "GetPrivateProfileStringA" _
      (ByVal lpSectionName As String, ByVal lpKeyName As String, _
       ByVal lpDefault As String, _
       ByVal lpReturnedString As StringBuilder, _
       ByVal nSize As Integer, _
       ByVal lpFileName As String) As Integer
 サービスをインストールするには、そのサービスを実行するユーザーのユーザー名と証明書、ならびにサービスから呼び出される実行ファイルへのパス情報を渡す必要があります。

Private Sub InstallService(ByVal sServiceName As String)
   Try
      Console.WriteLine("Running Install Service...")

      Dim sUserID As String = sDomainName & "\" & sUserID
      Dim sPassword As String = sPassword

      If IsServiceInstalled(sServiceName) Then _
         UninstallService(sServiceName)

      ' INSTALL
      Dim hSCM As IntPtr = OpenSCManager(Nothing, Nothing, _
         ServiceControlManagerEnum.AllAccess)
      If hSCM.ToInt32 = 0 Then
         Throw New Exception("Could not install service. [1]")
      Else
         Dim iServiceType As Integer = ServiceTypeEnum.Win32OwnProcess

      Dim hService As IntPtr = CreateService(hSCM, sServiceName, _
         sServiceName, ServiceControlManagerEnum.AllAccess, _
            iServiceType, ServiceTypeEnum.AutoStart, _
            ServiceTypeEnum.ErrorNormal, sPath, Nothing, Nothing, _
            Nothing, sUserID, sPassword)
      If hService.ToInt32 = 0 Then
         Throw New Exception("Could not install service. [2]")
      Else
         CloseServiceHandle(hService)
      End If

         CloseServiceHandle(hSCM)
         Call ControlServices(sServiceName, "Start")
      End If
   Catch ex As Exception
      Console.WriteLine(ex.Message)
      Console.ReadLine()
   End Try
End Sub
 サービスが存在するかどうかの確認は次の関数で行います。

Private Function IsServiceInstalled(ByVal sServiceName As String) _
   As Boolean
   Dim bResult As Boolean = False

   Dim oServiceArray() As ServiceProcess.ServiceController
   oServiceArray = ServiceProcess.ServiceController.GetServices

   For Each oServiceController _
      As ServiceProcess.ServiceController In oServiceArray
      If oServiceController.ServiceName.Trim.ToUpper = _
         sServiceName.Trim.ToUpper Then
         Dim i As New ServiceControllerPermissionAttribute _
            (Security.Permissions.SecurityAction.Demand)
         Dim d As New ServiceControllerPermission
         Try
            If d.Any Then
               d.ToString()
            End If
         Catch
         End Try
         bResult = True
         Exit For
      End If
   Next

   Return bResult
End Function
 アンインストールを行うプロシージャも、同様のコードで実現できます。

Private Sub UninstallService(ByVal sServiceName As String)
   Console.WriteLine("Running Uninstall Service...")

   If IsServiceInstalled(sServiceName) Then
      ' STOP SERVICE
      Call ControlServices(sServiceName, "Stop")
   Else
      Exit Sub
   End If

   Dim hSCM As IntPtr = OpenSCManager(Nothing, Nothing, _
      ServiceControlManagerEnum.AllAccess)
   If hSCM.ToInt32 = 0 Then
      Throw New Exception("Could not delete service. [1]")
   Else
      Dim hService As IntPtr = OpenService(hSCM, sServiceName, _
         ServiceAccessTypeEnum.AllAccess)
      If hService.ToInt32 = 0 Then
         ' TODO: FAILED
      Else
         If Not DeleteService(hService) Then
            Throw New Exception("Could not delete service. [2]")
         End If

         CloseServiceHandle(hService)
      End If

      CloseServiceHandle(hSCM)
   End If
End Sub
 ログオンユーザーを変更する場合、私の考える最も美しい方法は、サービスをアンインストールしてから、新たな証明書を用いて再インストールすることです。

 この記事のダウンロードサンプルには、ここで紹介したすべての関数を含むコンソールアプリケーションの「.sln」ファイルが収録されています。サンプルではアンインストールの関数を呼び出しているだけですが、この記事の内容を参考にすれば、メイン関数に起動/停止などの機能を簡単に追加できるでしょう。

著者紹介

aneezah(aneezah)
VB.NetおよびSQLに関して4年以上の経験を持つ開発者。
【関連記事】
『Windows 7』、6月のリリースを示す新たな根拠
Microsoft、『Windows 7』の公開ベータテストを開始
シマンテックが Mac 版ノートンの新製品を発表―1月9日から販売開始
DivX、H.264準拠の最新バージョン「DivX 7」を発表
オープンベースの「新勘定系システム」が十八銀行で稼働開始、日本ユニシス

New Topics

Special Ad

ゆりかごからロケットまで、すべての乗り物をエンジョイ
ゆりかごからロケットまで、すべての乗り物をエンジョイ えん乗り」は、ゆりかごからロケットまで、すべての乗り物をエンジョイする、ニュース、コラム、動画などをお届けします! てんこ盛りをエンジョイするのは こちらから

Hot Topics

IT Job

Interviews / Specials

Popular

Access Ranking

Partner Sites