【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
今度はちゃんと出てくるはずです。