[Dd]enzow(ill)? with DB and Python

DBとか資格とかPythonとかの話をつらつらと

Pythonを使ってWindows 上でps的な感じでプロセスの情報を取りたかった

普段業務はWindows上で行っています。Python何かを動かすときはLinux上であることが多いですが、たまにWindowsで、ジョブを動かさないといけないケースもあります。今回、ちょっとWindows上でプロセス一覧を取得しないといけないケースがあったので行った内容をまとめます。

対象環境

今回は以下の環境で実施しました。

  • Windows 7 64bit
  • Python 3.6

Windowsでプロセス一覧取得

Linuxでのps aux的な情報が欲しかったのですが、tasklistではexeまでしか取れずプロセスの引数等が取れませんでした。色々確認していったところ、どうやらwmic processで欲しい情報がとれることがわかりました。

wmic processはプロセスの引数だけでなく、そのプロセスの状態や統計等をSQLのように取得できるようですが一旦全データを取得できる以下のコマンドを使うことにしました。

DOS> wmic process get /FORMAT:LIST |head -n 20


Caption=System Idle Process
CommandLine=
CreationClassName=Win32_Process
CreationDate=20170911132520.625200+540
CSCreationClassName=Win32_ComputerSystem
CSName=B1609-21-01
Description=System Idle Process
ExecutablePath=
ExecutionState=
Handle=0
HandleCount=0
InstallDate=
KernelModeTime=4151831986137
MaximumWorkingSetSize=
MinimumWorkingSetSize=
Name=System Idle Process
OSCreationClassName=Win32_OperatingSystem
OSName=Microsoft Windows 7 Professional |C:\Windows|\Device\Harddisk0\Partition3

出力結果は扱いづらいですが、commandline等にとりあえず欲しい情報が含まれていることがわかりましたのでこれをPythonでラップしていきます。

出力結果について

この出力結果のフォーマットは以下でした。

  • 各行はkey=valueの形式で出力される
  • プロセスは空行で区切られている

割りとシンプルなフォーマットなのでパースするのは簡単ですね。

processを示すクラス

Dictでそのまま使ってもいいですが、とりあえず結果をしまうクラスを用意しました。

class WindowsProcess(object):
    """
    Windows上でのwmic processでの結果に紐づくクラス
    """

    def __init__(self, attributes):
        """
        sample:
        caption python.exe
        commandline python  -m unittest test_OsUtil.TestOsUtils.test_01_get_processes
        creationclassname Win32_Process
        :
        writetransfercount 4800
        :param attributes:
        """
        for k, v in attributes:
            # Pythonはlowerなので揃える
            setattr(self, k.lower(), v)

    def __str__(self):
        display_name = self.commandline if self.commandline else self.caption
        return "Process[{} {} {}]".format(
            self.parentprocessid,
            self.processid,
            display_name
        )


    def __repr__(self):
        return self.__str__()

本当は各キーの属性を先に用意するべきなのですが、ちょっと数が多い上にほとんど使わないと思ったのでsetattrで動的に設定するようにしてしまいました。とりあえず見たいのはparentprocessid(親プロセスID)やprocessid(プロセスID)、commandline(コマンドライン引数)ですので十分だと思います。

wmicのラッパー

先程のコマンドをPythonでラップします。subProcess.runで実行した結果素直に一行ずつ回して空行かきたらWindowsProcessを作っていく流れになっています。

import subprocess
import sys
import os

def get_processes():
    """
    プロセス一覧をwmic経由で取得する
    :param process_name:
    :return:
    """
    encoding = sys.stdout.encoding
    if not encoding:
        encoding = "UTF-8"
    # wmic process  get /FORMAT:LIST
    command_str = " ".join([
        "wmic",
        "process",
        "get",
        "/FORMAT:LIST"
    ])

    result = subprocess.run(command_str, shell=True, stdout=subprocess.PIPE)
    if not result.stdout:
        return []
    # 1行目はヘッダなので。
    process_list = []
    buf = []
    for line in result.stdout.decode(encoding).split("\r\r\n"):
        # 結果はプロセスごとに空行が挟まっている
        if line == "":
            if buf:
                target_process = WindowsProcess(buf)
                process_list.append(target_process)
                buf = []
            continue
        # コマンドの引数自体に=があるケースに備える
        key = line.split("=")[0]
        value = "=".join(line.split("=")[1:])
        buf.append([key, value])

    return process_list

まとめ

一応作ったスクリプトを全文載せておきます。あとは適当に属性を見ながらフィルタをPythonでかけて使えると思います。

# codingt:utf-8
import subprocess
import sys
import os

class WindowsProcess(object):
    """
    Windows上でのwmic processでの結果に紐づくクラス
    """

    def __init__(self, attributes):
        """
        :param attributes:[
            [key, value],
            [key, value],
        ]
        """
        for k, v in attributes:
            # Pythonはlowerなので揃える
            setattr(self, k.lower(), v)

    def __str__(self):
        display_name = self.commandline if self.commandline else self.caption
        return "Process[{} {} {}]".format(
            self.parentprocessid,
            self.processid,
            display_name
        )


    def __repr__(self):
        return self.__str__()




def get_processes():
    """
    プロセス一覧をwmic経由で取得する
    :param process_name:
    :return:
    """
    encoding = sys.stdout.encoding
    if not encoding:
        encoding = "UTF-8"
    # wmic process  get /FORMAT:LIST
    command_str = " ".join([
        "wmic",
        "process",
        "get",
        "/FORMAT:LIST"
    ])

    result = subprocess.run(command_str, shell=True, stdout=subprocess.PIPE)
    if not result.stdout:
        return []
    # 1行目はヘッダなので。
    process_list = []
    buf = []
    for line in result.stdout.decode(encoding).split("\r\r\n"):
        # 結果はプロセスごとに空行が挟まっている
        if line == "":
            if buf:
                target_process = WindowsProcess(buf)
                process_list.append(target_process)
                buf = []
            continue
        key = line.split("=")[0]
        value = "=".join(line.split("=")[1:])
        buf.append([key, value])

    return process_list

if __name__ == "__main__":
    for proc in get_processes():
        print(proc)

実行例

(py36_mm) > python ps.py |findstr ps.py
Process[6644 3344 python  ps.py ]
Process[6644 15768 findstr  ps.py]

ちゃんと思った結果になってますね^^