【C#】親プロセスの情報を取得する

普通だったらコマンドライン引数として呼び出し元のモジュールが渡すべきでしょう。ただ、もう本番稼働中かつその共通モジュールを呼びだしているモジュールが山ほどある、全部直してテストするなんてとても考えられない、なんて状況がたまにあります。

そんな時はSystem.Management名前空間を使いましょう、ってお話。

考え方

  1. System.Management名前空間にはWMIに関するクラスが集まっています。
  2. WMIのデフォルト名前空間であるroot\CIMV2にはWin32_Processクラスがあり、ProcessIdとParentProcessIdと言うプロパティを持っています。
  3. ManagementObjectSearcherクラスを使用することで、このプロパティをSQLライクなクエリで取得することが出来ます。
  4. ProcessクラスのGetProcessByIdメソッドに取得したParrentProcessIdを渡せば、親プロセスの情報を持ったProcessのインスタンスを新たに生み出すことが出来ます。

コード

呼び出されるモジュール

using System;
using System.Diagnostics;
using System.Management;

namespace ConsoleApplication1
{
    class Program
    {

        public static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("親プロセス名:{0}", GetParentModuleName());
                Console.WriteLine("引数:{0}", GetParentArguments());
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        private static string GetParentModuleName()
        {
            return Process.GetProcessById((int)GetParentProcessId()).ProcessName;
        }

        private static string GetParentArguments()
        {
            return Process.GetProcessById((int)GetParentProcessId()).StartInfo.Arguments;
        }

        private static uint GetParentProcessId()
        {
            var myProcId = Process.GetCurrentProcess().Id;
            var query = string.Format("SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = {0}", myProcId);

            using (var search = new ManagementObjectSearcher(@"root\CIMV2", query))
            //クエリから結果を取得
            using (var results = search.Get().GetEnumerator())
            {

                if (!results.MoveNext()) throw new ApplicationException("Couldn't Get ParrentProcessId.");

                var queryResult = results.Current;
                //親プロセスのPIDを取得
                return (uint)queryResult["ParentProcessId"];
            }
        }

    }
}

呼び出すモジュール

using System;
using System.Diagnostics;

namespace ParentTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var p = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = @"",
                    Arguments = "test",
                    CreateNoWindow = true,
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    WindowStyle = ProcessWindowStyle.Hidden,
                }
            };

            p.OutputDataReceived += new DataReceivedEventHandler((_, arg) => Console.WriteLine(arg.Data));
            p.Start();
            p.BeginOutputReadLine();
            p.WaitForExit();
        }
    }
}

実行結果

呼び出し元(ParentTest.exe)に「hoge」という引数を渡して実際に実行してみるとこんなメッセージがコマンドラインに出てきます。

親プロセス名:ParentTest
引数:

うん。引数が取れてないね。

Process.GetProcessByIdメソッドはあくまで新しいProcessのオブジェクトをくれるだけで、StartInfoまでは関連付けてくれないみたいです。

考え方を変えましょう。全部WMIから取ってきてしまえばいいのです。

コードを修正する

呼び出されるモジュールを変更してみます。

using System;
using System.IO;
using System.Diagnostics;
using System.Management;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {

        public static void Main(string[] args)
        {
            try
            {
                //Console.WriteLine("親プロセス名:{0}", ParentProcess.GetParentModuleName());
                //Console.WriteLine("引数:{0}", ParentProcess.GetParentArguments());

                Console.WriteLine("親プロセス名:{0}", GetParentModuleName());
                foreach (var arg in GetParentArguments().Skip(1))
                {
                    Console.WriteLine("引数:{0}", arg);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        private static string GetParentModuleName()
        {
            return Path.GetFileNameWithoutExtension((string)Search(GetParentProcessId(), "ExecutablePath"));
        }

        private static string[] GetParentArguments()
        {
            return ((string)Search(GetParentProcessId(), "CommandLine")).Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
        }

        private static uint GetParentProcessId()
        {
            return (uint)Search(Process.GetCurrentProcess().Id, "ParentProcessId");
        }

        private static object Search(int PID, string property)
        {
            var query = string.Format("SELECT {0} FROM Win32_Process WHERE ProcessId = {1}", property, PID);

            using (var search = new ManagementObjectSearcher(@"root\CIMV2", query))
            //クエリから結果を取得
            using (var results = search.Get().GetEnumerator())
            {

                if (!results.MoveNext()) throw new ApplicationException(string.Format("Couldn't Get {0}.", property));

                var queryResult = results.Current;
                return queryResult[property];
            }
        }

        private static object Search(uint PID, string property)
        {
            return Search((int)PID, property);
        }

    }
}

CommandLineの返り値を半角スペースでSplitし、indexを0で取り出すとモジュールへのパスを取り出すことが出来ますが、今回は不要なのでSkip(1)を指定しています。

再実行

親プロセス名:ParentTest
引数:hoge

今度はちゃんと出てくるはずです。