◆VBA (VB6)でのWindows Service制御 - Win32 API編
[ 上へ ] [ Service - VB2005 - 前準備 ] [ Service - VBA(VB6) - Win32 API編 ] [ Service - VBA(VB6) - WMI編 ] [ Service - VC++ 2005 - Win32API編 ]
始める前に、 ここを読んでください
VBA(VB6)でサービスを制御する場合は便利なクラスライブラリもないですから VB2005と比べて大変そうに思われます。これらの言語はCOMと親和性が高いですから、 サービスを扱うコンポーネントがあれば楽に実装できるでしょう。なければ、 最後の手段Win32 APIでゴリゴリコーディングになります。
実はそんなコンポーネントがあるのですが、まずは先に苦労をしておくと そのコンポーネントのありがたさがわかるでしょう。ということでWin32APIを使った方法を 先に書いてみます。
実装環境はVBAを扱えるプラットフォームでしたらなんでもいいのですが、今回は Excel 2000 VBA - Window XPを前提にします。
早速、Excel 2000を起動して、Alt + F11でVBEを起動してください。
次にツールバーの挿入(I)→標準モジュール(M)で新しいモジュールを作成してください。
ここにすべてのコードを記述していきます。
・Win32APIの宣言
VB6またはOffice 2000, 2002 Developerユーザは Win32Api.txt というWin32API(以後APIと呼びます)の宣言の一覧が書かれたファイルを使って宣言すると思います。 それ以外のユーザでもVectorあたりに同じようなファイルがあったと思いますが、ないと 宣言することがたいへんなので ここにおいておきます。
ところがこれがなかなか曲者で、偶にそのままコピペして使おうとすると
最悪Officeが落ちてしまうこともあります。(私も何度も経験があります。)
>もともとAPIはC言語で書かれていてDeclareステートメントでVB/VBAでも使えるように
したものなので、最終的には
MSDNで使用しようとするC言語でのAPIの宣言、特に引数・戻値の型を確認して
その上で注意してVBA/VB6で宣言しなおさないといけないこともでてくるでしょう。
・サービスの状態の取得
まず、サービスの状態を取得するAPIを宣言してみます。使用するAPIは
OpenSCManager・・・・・・・・サービス制御マネージャとの接続・データベースOpen
OpenService・・・・・・・・・・・サービスハンドルのOpen
QueryServiceStatus・・・・ サービスの状態の取得
CloseServiceHandle ・・・・・サービスハンドルのClose
です。 Win32Api.txt からコピーすると
'サービス制御マネージャ接続・データベースOpen Private Declare Function OpenSCManager Lib "advapi32.dll" Alias "OpenSCManagerA" _ (ByVal lpMachineName As String, _ ByVal lpDatabaseName As String, _ ByVal dwDesiredAccess As Long) As Long 'サービスOpen Private Declare Function OpenService Lib "advapi32.dll" Alias "OpenServiceA" _ (ByVal hSCManager As Long, _ ByVal lpServiceName As String, _ ByVal dwDesiredAccess As Long) As Long 'サービス状態取得 Private Declare Function QueryServiceStatus Lib "advapi32.dll" _ (ByVal hService As Long, _ lpServiceStatus As SERVICE_STATUS) As Long 'ハンドルClose Private Declare Function CloseServiceHandle Lib "advapi32.dll" _ (ByVal hSCObject As Long) As Long |
Win32Api.txt がないと自身でMSDNのC言語での宣言を見て、引数の型、戻値の型を設定しないと いけないので、C言語をある程度理解できてないとつらいかもしれません。
次に、 OpenSCManager の第三引数で指定する定数、 OpenService の第三引数で指定する定数、QueryServiceStatusでサービスの状態を受け取る SERVICE_STATUS 構造体、サービスの状態を示す定数(SERVICE_STATUSのdwCurrentStateメンバが保持する定数)も 定義します。
定数はMSDNを見ると様々な値がありますが、各値の説明を読むと、 OpenSCManagerの第三引数はサービス制御マネージャとの接続を有効にするだけでいいので SC_MANAGER_CONNECTを定義します。 OpenServiceの第三引数は、状態の取得だけなのでSERVCE_QUERY_STATUSだけです。 サービスの状態を示す定数はMSDNのSERVICE_STATUSのdwCurrentStateの説明で示されて いる値に加えてサービス状態の取得失敗のための定数を加えます。
Win32Api.txt から各値をコピーします。
準備ができたので実装します。
サービスの状態を取得するFunctionプロシージャをGetServiceStatusとし、引数をサービス名、戻値を
先に定義したSERVICE_XXXX定数のうちの1つとします。
上記の宣言に続いて以下を記述してください。
(A)のOpenSCManagerの
第一引数はコンピュータ名を指定しますが、ローカルコンピュータを指定する
時は空文字列か NULL(0)を代入しますが、DeclareでString型を宣言していますので、
""とします。もし、NULL(0)を設定するのでしたら第一引数を
ByVal lpMachineName As LongとDeclareステートメントを修正することになります。
Private Declare Function OpenSCManager Lib "advapi32.dll" Alias "OpenSCManagerA" _ (ByVal lpMachineName As Long, _ ByVal lpDatabaseName As String, _ ByVal dwDesiredAccess As Long) As Long vlHSCManager = OpenSCManager( 0&, "ServicesActive", SC_MANAGER_CONNECT)
第二引数はデータベース名の指定でMSDNにはSERVICES_ACTIVE_DATABASE、 またはNULL(0)を設定するように書かれています。ここもDeclareでString型として 宣言しているので、SERVICES_ACTIVE_DATABASEとしたいのですが、この実体は プラットフォームSDKのwinsvc.hヘッダファイルに
#define SERVICES_ACTIVE_DATABASE SERVICES_ACTIVE_DATABASEW #define SERVICES_ACTIVE_DATABASEW L"ServicesActive"
と定義されています。C言語なのでわからないかもしれませんが、要はServicesActiveは SERVICES_ACTIVE_DATABASEとして定義されていますよということです。ということで この文字列を指定します。ここにNULL(0)を設定したいのでしたら、第一引数の時と同様に Declareステートメントを修正することになります。
第三引数は先ほど定義したSC_MANAGER_CONNECTを設定します。
OpenSCManagerが失敗した場合、0を返すので、GetServiceStatusの戻値を0にしてDo〜Loopを抜けます。
(B)の第一引数は(A)が成功した場合のハンドルを指定します。 第二引数はOpenしようとするサービス名、第三引数はサービスの状態を取得するだけなので SERVICE_QUERY_STATUSだけを設定すればいいです。
サービスのハンドルが取得できたら(C)の第二引数の SERVICE_STATUS 構造体に結果が格納されます。 サービスの状態を示すメンバはdwCurrentStateなのでこれをGetServiceStatus関数の戻値として返します。 なお、他のメンバについては必要が出てきた時点で書きたいと思います。詳しくはMSDNをご覧ください。
・サービス状態の変更
サービス状態の取得はできたので今度は変更です。変更はさらにたいへんになります。 前項で使用したAPIの他にサービスを制御する
StartService
・・・・・・サービスの開始
ControlService
・・・・サービスの他の制御
と、サービス状態が変更するまで待機する
を使用します。では Win32Api.txt からGetServiceStatus関数の上にコピーします。
'サービス開始 Private Declare Function StartService Lib "advapi32.dll" Alias "StartServiceA" _ (ByVal hService As Long, _ ByVal dwNumServiceArgs As Long, _ ByVal lpServiceArgVectors As Long) As Long 'サービス状態変更 Private Declare Function ControlService Lib "advapi32.dll" _ (ByVal hService As Long, _ ByVal dwControl As Long, _ lpServiceStatus As SERVICE_STATUS) As Long Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) |
そして、OpenServiceの第三引数で前回はSERVICE_QUERY_STATUSだけを指定しましたが、
今度は状態を変更するのでさらに
SERVICE_START
SERVICE_STOP
SERVICE_PAUSE_CONTINUE
を使います。
ControlServiceの第二引数で指定する制御コードの定数を定義します。Start以外の
SERVICE_CONTROL_STOP
SERVICE_CONTROL_PAUSE
SERVICE_CONTROL_CONTINUE
を定義します。
サービスの状態を SERVICE_STATUS構造体 に受け取った後、そのメンバのdwControlsAcceptedで 停止、再開、一時停止ができるかどうかを確認しなければならないので、ここに受け取ることができる 定数を定義します。
Win32Api.txt からSleep関数のしたあたりにでもコピーしましょう。
'OpenService用定数 Private Const SERVICE_QUERY_STATUS = &H4 'ここは既に定義済み Private Const SERVICE_START = &H10 Private Const SERVICE_STOP = &H20 Private Const SERVICE_PAUSE_CONTINUE = &H40 'ControlService用定数 Private Const SERVICE_CONTROL_STOP = &H1 Private Const SERVICE_CONTROL_PAUSE = &H2 Private Const SERVICE_CONTROL_CONTINUE = &H3 'SERVICE_STATUS構造体のメンバdwControlsAcceped用定数 Private Const SERVICE_ACCEPT_PAUSE_CONTINUE = &H2 Private Const SERVICE_ACCEPT_STOP = &H1 |
さらに、サービスを変更する動作を列挙定数として VB2005の事例 の時と同様に次のように定義します。
Public Enum SERVICE_ACTION Svc_Start = 1 Svc_Stop = 2 Svc_Pause = 3 Svc_Continue = 4 End Enum |
それではサービスの状態を変更する関数を実装します。流れとしては
QueryServiceStatus APIで現在のサービスの状態を取得し、その値によってStartService、
またはControlServiceを実行します。
プロシージャ名はChangeServiceStatus、引数はString型のサービス名とSERVICE_ACTION列挙型の
サービスの状態を変更する値。戻値は状態が変更された場合はTrue、失敗または状態が変更され
なかったときはFalseのBoolean型とします。
'サービス状態変更 '引数 argsName : 状態を変更しようとするサービス名 ' argeAction: 停止・開始・停止・一時停止を表す列挙定数 '戻値 サービス状態が変更したときTrue、エラーまたは状態が変更しないときFalse Public Function ChangeServiceStatus(ByVal argsName As String, _ ByVal argeAction As SERVICE_ACTION) As Boolean Dim vyRet As Boolean '戻値用 Dim vlHSCManager As Long 'サービス制御マネージャのデータベースのハンドル Dim vlHService As Long 'サービスのハンドル Dim vtSvcStatus As SERVICE_STATUS 'SERVICE_STATUS構造体 Do 'サービス制御マネージャのデータベースハンドル取得 vlHSCManager = OpenSCManager("", "ServicesActive", SC_MANAGER_CONNECT) '(A) If vlHSCManager = 0 Then vyRet = False: Exit Do End If 'サービスハンドル取得 vlHService = OpenService(vlHSCManager, argsName, SERVICE_QUERY_STATUS _ Or SERVICE_START _ Or SERVICE_STOP _ Or SERVICE_PAUSE_CONTINUE) '(B) If vlHService = 0 Then vyRet = False: Exit Do End If 'サービスの状態取得 If QueryServiceStatus(vlHService, vtSvcStatus) = 0 Then '(C) vyRet = False: Exit Do End If Do '(a) Select Case vtSvcStatus.dwCurrentState '(D) Case SERVICE_STOPPED '現在停止中 '(E) If argeAction = Svc_start Then '(F) If StartService(vlHService, 0&, 0&) = 0 Then 'サービス開始 '(G) vyRet = False: Exit Do End If '状態変更確認までループ Do Until QueryServiceStatus(vlHService, vtSvcStatus) = 0 '(H) If vtSvcStatus.dwCurrentState = SERVICE_RUNNING Then Exit Do '(I) End If Sleep IIf(vtSvcStatus.dwWaitHint > 500, 500, _ vtSvcStatus.dwWaitHint) '(J) Loop vyRet = True End If Exit Do Case SERVICE_RUNNING '現在実行中 '(K) If (argeAction = Svc_stop) And _ ((vtSvcStatus.dwControlsAccepted And _ SERVICE_ACCEPT_STOP) > 0) Then '(L) 'サービス停止 If ControlService(vlHService, SERVICE_CONTROL_STOP, _ vtSvcStatus) = 0 Then '(M) vyRet = False: Exit Do End If '状態変更確認までループ Do Until QueryServiceStatus(vlHService, vtSvcStatus) = 0 If vtSvcStatus.dwCurrentState = SERVICE_STOPPED Then Exit Do End If Sleep IIf(vtSvcStatus.dwWaitHint > 500, 500, _ vtSvcStatus.dwWaitHint) Loop vyRet = True ElseIf (argeAction = Svc_pause) And _ ((vtSvcStatus.dwControlsAccepted And _ SERVICE_ACCEPT_PAUSE_CONTINUE) > 0) Then '(N) 'サービス一時停止 If ControlService(vlHService, SERVICE_CONTROL_PAUSE, _ vtSvcStatus) = 0 Then vyRet = False: Exit Do End If '状態変更確認までループ Do Until QueryServiceStatus(vlHService, vtSvcStatus) = 0 If vtSvcStatus.dwCurrentState = SERVICE_PAUSED Then Exit Do End If Sleep IIf(vtSvcStatus.dwWaitHint > 500, 500, _ vtSvcStatus.dwWaitHint) Loop vyRet = True End If Exit Do Case SERVICE_PAUSED '現在一時停止 '(O) If (argeAction = Svc_continue) And _ ((vtSvcStatus.dwControlsAccepted And _ SERVICE_ACCEPT_PAUSE_CONTINUE) > 0) Then 'サービス再開 If ControlService(vlHService, SERVICE_CONTROL_CONTINUE, _ vtSvcStatus) = 0 Then vyRet = False: Exit Do End If '状態変更確認までループ Do Until QueryServiceStatus(vlHService, vtSvcStatus) = 0 If vtSvcStatus.dwCurrentState = SERVICE_RUNNING Then Exit Do End If Sleep IIf(vtSvcStatus.dwWaitHint > 500, 500, _ vtSvcStatus.dwWaitHint) Loop vyRet = True ElseIf (argeAction = Svc_stop) And _ ((vtSvcStatus.dwControlsAccepted And _ SERVICE_ACCEPT_STOP) > 0) Then 'サービス停止 If (ControlService(vlHService, SERVICE_CONTROL_STOP, _ vtSvcStatus) = 0) Then vyRet = False: Exit Do End If '状態変更確認までループ Do Until QueryServiceStatus(vlHService, vtSvcStatus) = 0 If vtSvcStatus.dwCurrentState = SERVICE_STOPPED Then Exit Do End If Sleep IIf(vtSvcStatus.dwWaitHint > 500, 500, _ vtSvcStatus.dwWaitHint) Loop vyRet = True End If Exit Do Case Else 'Pending状態の時 '(P) Sleep IIf(vtSvcStatus.dwWaitHint > 500, 500, _ vtSvcStatus.dwWaitHint) If QueryServiceStatus(vlHService, vtSvcStatus) = 0 Then Exit Do End If End Select Loop '(a') Exit Do Loop '各ハンドルのclose If vlHService <> 0 Then CloseServiceHandle vlHService If vlHSCManager <> 0 Then CloseServiceHandle vlHSCManager '戻値の設定 ChangeServiceStatus = vyRet End Function |
(A),(B),(C)はGetServiceStatus のときとほぼ同じですが、(B)のOpenServiceの第三引数はサービスの 状態を変更するので、それを許可できるように定数を加えます。
(C)で SERVICE_STATUS 構造体のdwCurrentStateに現在のサービスの状態を受け取っているので (D)以下で場合分けをしています。
(E)のサービスが停止中の時は変更できる状態は実行中、つまりサービス開始アクション( Svc_Start)なので それ以外は何もしないようにします。(F)
(G)でサービスを開始しますが直ちに状態は変更しないので、 (H)で状態が変更するまでQueryServiceStatusを実行して無限ループに入ります。 ループ内でQueryServiceStatusを実行することによって受け取ったdwCurrentStateがRunningに なったらループを抜けます。(I) 状態がまだRunningになっていない場合は、 (J)でSleepして制御を一時停止します。どれだけ停止するかを指定するかを第一引数で指定しますが、 SERVICE_STATUS 構造体のインスタンスvtSvcStatusのdwWaitHintは状態を変更するまでに必要とされる 見積もり時間(ミリ秒単位)なので、これを指定します。
(K)の実行中の時は、変更できる状態は停止中と一時停止、 つまり停止アクション(Svc_Stop) (L)と、一時停止アクション(Svc_Pause) (N)なので、それぞれ場合分けをします。(L)と(N)で場合分けすると同時に、そのサービスが SERVICE_STATUS 構造体のdwControlsAcceptedメンバで停止や一時停止ができるかも確認しています。
(M)でサービスを停止させ、サービス開始の時と同様の処理を行います。
(O)のサービス一時停止のときは、再開アクション( Svc_Continue)と停止アクション( Svc_Stop)で上記と同様の 処理をします。
現在のサービスの状態が停止中、実行中、一時停止中以外は (P)のルーチンが実行されます。これは サービスの状態 がPendingということなのでSleepで制御を一時停止してループしています。((a)〜(a')のループ)
最後に 各ハンドルをCloseして戻値を返します。
これでサービスの状態の取得と変更ができるようになったので、 次回はスタートアップの種類の取得/変更について書きます。