怠日記

写真・金魚・昆虫・プログラミングの趣味を語るサイトです。似たようなことをnoteにも書いたり書いてなかったり。

VBScript - 多重起動を禁止する(WMI利用)

スクリプトの処理中に、同じスクリプトを起動されたくないケースがある。
多重起動を禁止するにはどうすれば良いのか。

概要

WMI (Windows の管理データ)の Win32_Process で Windows 上のプロセスを確認できる。

この Win32_Process より自分自身のスクリプト名を実行しているプロセスを取得することで、多重起動を禁止できる。

コードサンプル

以下は自身と同じスクリプト名を実行しているプロセスを取得し、結果が複数だった場合にそのプロセス情報を出力するコード。

Option Explicit
'自分自身が wscript.exe または cscript.exe で実行されているか確認するクエリ
Dim query
query = " SELECT *" & _
         "   FROM Win32_Process" & _
         "  WHERE (Caption = 'wscript.exe' OR Caption = 'cscript.exe') " & _
         "    AND CommandLine LIKE '%" & WScript.ScriptName & "%'"
'SWbemLocator オブジェクトを生成
With CreateObject("WbemScripting.SWbemLocator")
    'ローカルPCに接続
    With .ConnectServer
        'クエリを実行
        Dim processes
        Set processes = .ExecQuery(query)
        'クエリの結果が2件以上あれば、同一スクリプトが実行されていると判断できる
        If processes.Count > 1 Then
            WScript.Echo "多重起動しています"
            
            'プロセス情報を出力
            Dim proc
            For Each proc In processes
                WScript.Echo "----------------------------------------"
                WScript.Echo "Name: " & proc.Name
                WScript.Echo "Caption: " & proc.Caption
                WScript.Echo "CommandLine: " & proc.CommandLine
                WScript.Echo "ProcessID: " & proc.ProcessID
            Next
        End If
        Set processes = Nothing
    End With
End With

多重起動を検知した場合、次のような出力結果となる。

(上記のスクリプトを prohibition_multiple_startup_.vbs という名前で保存した。それをダブルクリックで実行後、CScript でも実行した場合)

C:\tmp\wsh>CScript prohibition_multiple_startup_.vbs
多重起動しています
----------------------------------------
Name: wscript.exe
Caption: wscript.exe
CommandLine: "C:\WINDOWS\System32\WScript.exe" "C:\tmp\wsh\prohibition_multiple_startup_.vbs"
ProcessID: 36048
----------------------------------------
Name: cscript.exe
Caption: cscript.exe
CommandLine: CScript  prohibition_multiple_startup_.vbs
ProcessID: 16796

多重起動を禁止するには、クエリの実行結果が2件以上だったときに、メッセージを表示してスクリプトを終了させれば良いだろう。

'クエリの結果が2件以上あれば、同一スクリプトが実行されていると判断できる
If processes.Count > 1 Then
    WScript.Echo "すでに実行されているため終了します。"
    WScript.Quit 1
End If

関数化

汎用的に使えるよう関数化しておく。

引数 piScriptName には自分自身のスクリプト名を指定する。
すでに実行されているなら True が、そうでないなら False が返ります。

'''
''' 指定されたスクリプト名が実行されているか判定します。
''' 実行中なら True を、そうでないなら False を返します。
'''
Function IsRunning (ByVal piScriptName)
    '戻り値初期化
    IsRunning = False
    '自分自身が wscript.exe または cscript.exe で実行されているか確認するクエリ
    Dim query
    query = " SELECT *" & _
             "   FROM Win32_Process" & _
             "  WHERE (Caption = 'wscript.exe' OR Caption = 'cscript.exe') " & _
             "    AND CommandLine LIKE '%" & piScriptName & "%'"
    'SWbemLocator オブジェクトを生成
    With CreateObject("WbemScripting.SWbemLocator")
        'ローカルPCに接続
        With .ConnectServer
            'クエリを実行
            Dim processes
            Set processes = .ExecQuery(query)
            'クエリの結果が2件以上あれば、同一スクリプトが実行されていると判断できる
            If processes.Count > 1 Then
                IsRunning = True
            End If
            Set processes = Nothing
        End With
    End With
End Function

課題

短時間に複数実行したら、すべて「実行中」と検知されてしまった。
(今回はエクスプローラからEnterキーを連続して叩いた)

ExecQuery でプロセス情報を取得する前に複数起動してしまうと、このようになるらしい。

両方実行されるよりはマシだが、いずれ改善したい。