【C#】親プロセスの情報を取得する
普通だったらコマンドライン引数として呼び出し元のモジュールが渡すべきでしょう。ただ、もう本番稼働中かつその共通モジュールを呼びだしているモジュールが山ほどある、全部直してテストするなんてとても考えられない、なんて状況がたまにあります。
そんな時はSystem.Management名前空間を使いましょう、ってお話。
考え方
- System.Management名前空間にはWMIに関するクラスが集まっています。
- WMIのデフォルト名前空間であるroot\CIMV2にはWin32_Processクラスがあり、ProcessIdとParentProcessIdと言うプロパティを持っています。
- ManagementObjectSearcherクラスを使用することで、このプロパティをSQLライクなクエリで取得することが出来ます。
- 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
今度はちゃんと出てくるはずです。