Edited at

C#でPowerShellを使ってCOMオブジェクトブラウザを作ってみた

COMオブジェクトのメンバー一覧を取得する (PowerShell)

を応用してブラウザつくってみた。

※oleview.exeを持ってればいらない説…(まだ使ってないのでわからない)

ちなみにCOMオブジェクトでない普通の.Netのクラス(System.IO.Fileとか)であれば、

C#リフレクションTIPS 55連発

が参考になります。


画面キャプチャ

image.png


ソースコード

using System;

using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
using System.Management.Automation;

public class ProgIdInfo
{
private PSObject _psObj; // ProgID レジストリキー情報のPSObject
private string _ProgID;
private string _CLSID;
private string _ValueOfCLSID;

public string ProgID { get{return _ProgID;} }
public string CLSID { get{return _CLSID;} }
public string ValueOfCLSID { get{return _ValueOfCLSID;} }

// レジストリのキーを返すので取り扱い注意
// (戻ってきた PSObject に対してSetやDelete等の書き込み系のメソッドを使わないように。)
// ProgIDの一覧取得については、PowerShell使わなくてもレジストリ検索を実装すればできなくはない。その方が処理速いかも
private static Collection<PSObject> GetProgIdCollection()
{
using ( var invoker = new RunspaceInvoke() ) {
return invoker.Invoke(@"dir REGISTRY::HKEY_CLASSES_ROOT\CLSID -Include PROGID -Recurse");
}
}

public static List<ProgIdInfo> GetProgIDs()
{
var ret = new List<ProgIdInfo>();

var psObjs = GetProgIdCollection();

foreach ( var psObj in psObjs ) {
ProgIdInfo t = NewProgIdInfo(psObj);
ret.Add(t);
}

return ret;
}

private static ProgIdInfo NewProgIdInfo(PSObject psObj)
{
var ret = new ProgIdInfo();
ret._psObj = psObj;
ret._ProgID = "";
ret._CLSID = "";
ret._ValueOfCLSID = "";

ret._ProgID = PSObject_Invoke_GetValue(psObj); // ProgID の取得

string[] regKeyPathOfProgID = (psObj.Members["Name"].Value??"").ToString().Split('\\');
// ["Name"] は HKEY_CLASSES_ROOT\CLSID\{xxxx..-xx..}\ProgID が返る

if ( regKeyPathOfProgID.Length >= 2 ) {
ret._CLSID = regKeyPathOfProgID[regKeyPathOfProgID.Length-2]; // parent of last node
}
string parentKey = MyPartialJoin("\\", regKeyPathOfProgID, 1, 1); // 先頭の @"HKEY_CLASSES_ROOT\" と末尾の @"\ProgID" を捨てる
ret._ValueOfCLSID = GetClassRootRegisterValue(parentKey, ""); // 既定の値を読み出す

return ret;
}

// レジストリアクセス
private static string GetClassRootRegisterValue(string regKeyPath, string regValueName)
{
if ( regKeyPath == null || regKeyPath == "" ) {
return "";
}

Microsoft.Win32.RegistryKey regkey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(regKeyPath);
if (regkey == null) {
return "";
}

try{
return (regkey.GetValue(regValueName)??"").ToString();
}
finally{
regkey.Close();
}
}

private static string MyPartialJoin(string separator, string[] ss, int startIndex, int numOfOmitLast)
{
// CopyメソッドなりLINQなり使った方が綺麗な気がするが、とりあえずこれで。
int n = ss.Length - startIndex - numOfOmitLast;
if ( n <= 0 ) { return ""; }
else {
string[] ss2 = new String[n];
for ( int i = startIndex; i < ss.Length - numOfOmitLast; i++) {
ss2[i-startIndex] = ss[i];
}
return String.Join(separator, ss2);
}
}

private static string PSObject_Invoke_GetValue(PSObject psObj)
{
object t = psObj.Methods["GetValue"].Invoke(new object[]{""}); // 規定値を取得する。 // レジストリアクセスでやってもよい
return (t??"").ToString(); // Microsoft PenInputPanel ControlがなぜかProgIdの値が空欄なので、処置しないと例外発生する
}
}

public class ComObjectMember
{
private PSObject _psObj;
private string _MemberTypeText;
private string _ReturnTypeText;
private string _MemberName;
private string _DefinitionText;

private static Regex r = new Regex(@"^([^ ]+)");

public string MemberTypeText { get{return _MemberTypeText;} }
public string ReturnTypeText { get{return _ReturnTypeText;} }
public string MemberName { get{return _MemberName;} }
public string DefinitionText { get{return _DefinitionText;} }

private static Collection<PSObject> _GetMembers_PS(string comObjectName)
{
Regex r = new Regex(@"^[ ._0-9A-Za-z]+$"); // MS Remote など、スペースが入っている輩がいるので注意

if ( r.IsMatch(comObjectName) ) { // 危険なパラメータが混入しないように簡易チェック
string[] source = new string[] {
"$t = New-Object -ComObject \"" + comObjectName + "\"",
"Get-Member -InputObject $t"
}; // memo: ReleaseComObjectしないとよろしくない気がする

using ( var invoker = new RunspaceInvoke() ) {
try {
Collection<PSObject> tmp = invoker.Invoke(source[0]);
Collection<PSObject> result = invoker.Invoke(source[1]);
return result;
}
catch (System.Management.Automation.CmdletInvocationException e) {
// memo:
// VideoRenderCtl.VideoRenderCtl.1 選んだらCreateInstanceに失敗したっぽい例外を吐いたので
// とりあえずcatchしておく
Console.WriteLine(e);
return new Collection<PSObject>();
}
}
}
else {
return new Collection<PSObject>();// 要素数 0 の Collection を返しておく
}
}

public static List<ComObjectMember> GetMembers(string comObjectName)
{
var psObjs = _GetMembers_PS(comObjectName);
var ret = new List<ComObjectMember>();

foreach ( var psObj in psObjs ) {
ret.Add(NewComObjectMember(psObj));
}

return ret;
}

private static ComObjectMember NewComObjectMember(PSObject psObj)
{
var ret = new ComObjectMember();
ret._psObj = psObj;

// Regex r = new Regex(@"^([^ ]+)");
// フォーマットが複雑すぎて分解することが困難なため、戻り値の型(っぽい)部分だけを取り出す。
// - overloadが1行に入ってたりする (例: System.Random)
// - 仮引数名があったりなかったり
// - ()がない場合もある
// - ()がネストしている場合もある
// - 後ろに{get} {set} がつく場合もある

Match m = r.Match(psObj.Members["Definition"].Value.ToString());
//Match m = r.Match(psObj.ToString());

if ( m.Success ) {
ret._MemberTypeText = (psObj.Members["MemberType"].Value).ToString(); // PSMemberTypes (Method / Property など)
ret._ReturnTypeText = m.Groups[1].Value;
ret._MemberName = psObj.Members["Name"].Value.ToString();
ret._DefinitionText = psObj.Members["Definition"].Value.ToString();
}
else {
// 想定外の形式の場合は、とりあえずToString()して DefinitionText に入れる
ret._MemberTypeText = "";
ret._ReturnTypeText = "";
ret._MemberName = "";
ret._DefinitionText = psObj.ToString();
}

return ret;
}

}

class MainForm:Form
{
Button btnGetProgID;
Button btnGetMembers;
// CheckBox chkFilter;
TextBox txtFilter;
SplitContainer spl;
TextBox txtProgID;
ListView lsvProgID;
ListView lsvMemb;
MyListViewContextMenuInfo contextMenuInfo;

List<ProgIdInfo> allProgIDs; // filter処理を入れたいので、ListViewに直接持たせるのはやめた。

MainForm()
{
Text = "COM Object Browser";

allProgIDs = new List<ProgIdInfo>();

spl = new SplitContainer();
spl.Location = new Point(0, 0);
spl.Dock = DockStyle.Fill;
spl.Orientation = Orientation.Vertical;
Controls.Add(spl);

////// 左 Panel ここから
btnGetProgID = new Button();
btnGetProgID.Location = new Point(0, 0);
btnGetProgID.Size = new System.Drawing.Size(80,25);
btnGetProgID.Text = "Get ProgIDs";
btnGetProgID.Click += (sender,e)=>{ReloadProgIDs();};
spl.Panel1.Controls.Add(btnGetProgID);

txtFilter = new TextBox();
txtFilter.Location = new Point(btnGetProgID.Right+10, 0);
txtFilter.Size = new System.Drawing.Size(90,25);
txtFilter.Text = "";
txtFilter.TextChanged += (sender,e)=>{UpdateLsvProgID();}; // 入力中も変化するので鬱陶しいが妥協する。
spl.Panel1.Controls.Add(txtFilter);
new ToolTip().SetToolTip(txtFilter, "Filter text for <ProgID>, <Value of CLSID>\r\nTo show all, clear the TextBox content.");

lsvProgID = new ListView();
lsvProgID.Location = new Point(0, 30);
lsvProgID.View = View.Details;
lsvProgID.FullRowSelect =true;
lsvProgID.GridLines = true;
lsvProgID.Columns.Add("ProgID", 210, HorizontalAlignment.Left);
lsvProgID.Columns.Add("CLSID", 210, HorizontalAlignment.Left);
lsvProgID.Columns.Add("Value of CLSID", 210, HorizontalAlignment.Left);
lsvProgID.SelectedIndexChanged += LsvProgID_SelectedIndexChanged;
lsvProgID.ColumnClick += LsvProgID_ColumnClick;
lsvProgID.ListViewItemSorter = new ListViewItemComparer(0); // index 0 のcolumnでソート設定しておく
spl.Panel1.Controls.Add(lsvProgID);
////// 左 Panel ここまで

////// 右 Panel ここから
txtProgID = new TextBox();
txtProgID.Location = new Point(0, 0);
txtProgID.Width = 250;
txtProgID.Text = "InternetExplorer.Application";
spl.Panel2.Controls.Add(txtProgID);
new ToolTip().SetToolTip(txtProgID, "ProgID");

btnGetMembers = new Button();
btnGetMembers.Location = new Point(txtProgID.Right+10, 0);
btnGetMembers.Size = new System.Drawing.Size(100,25);
btnGetMembers.Text = "Get Members";
btnGetMembers.Click += (sender,e)=>{ReloadLsvMemb(txtProgID.Text);};
spl.Panel2.Controls.Add(btnGetMembers);
new ToolTip().SetToolTip(btnGetMembers, "Get Members from ProgID in the TextBox");

lsvMemb = new ListView();
lsvMemb.Location = new Point(0, 30);
lsvMemb.View = View.Details;
lsvMemb.FullRowSelect =true;
lsvMemb.GridLines = true;
lsvMemb.Columns.Add("MemberType", 70, HorizontalAlignment.Left);
lsvMemb.Columns.Add("ReturnType", 100, HorizontalAlignment.Left);
lsvMemb.Columns.Add("MemberName", 130, HorizontalAlignment.Left);
lsvMemb.Columns.Add("Definition", 350, HorizontalAlignment.Left);
spl.Panel2.Controls.Add(lsvMemb);

{
var a = new ContextMenuStrip();
a.Items.Add(new ToolStripMenuItem("Copy ReturnType", null, GetSelectedItems, "CopyReturnType"));
a.Items.Add(new ToolStripMenuItem("Copy MemberName", null, GetSelectedItems, "CopyMemberName"));
a.Items.Add(new ToolStripMenuItem("Copy Definition", null, GetSelectedItems, "CopyDefinition"));

a.Opening += LsvMemb_ContextMenuStrip_Opening;
lsvMemb.ContextMenuStrip = a;
}
////// 右 Panel ここまで

ClientSize = new System.Drawing.Size(800,530);

Load += Form_Resize;
Resize += Form_Resize;
ResizeEnd += Form_Resize;
spl.SplitterMoving += Form_Resize;
spl.SplitterMoved += Form_Resize;
}

void ClipboardSetText(string s)
{
if ( s==null || s=="" ){return;}

try {
Clipboard.SetText(s);
}
catch ( ExternalException ) { // クリアに失敗
// 1回だけリトライする
try {
Clipboard.SetText(s);
}
catch ( ExternalException e2 ) {
Console.WriteLine(e2);
}
}
}

void LsvMemb_ContextMenuStrip_Opening(object sender, CancelEventArgs e)
{
// https://qiita.com/Toraja/items/51ffc5cbfa0b9f8e154f
Point p = lsvMemb.PointToClient(Cursor.Position);
ListViewItem item = lsvMemb.HitTest(p).Item;

contextMenuInfo = null;

if (item == null) {
e.Cancel = true;
}
else if ( item.Bounds.Contains(p) ) {
contextMenuInfo = new MyListViewContextMenuInfo(lsvMemb, item);
}
else {
e.Cancel = true;
}
}

// ToolStripMenuItem.Click イベント
void GetSelectedItems(object sender, EventArgs e)
{
var mi = (ToolStripMenuItem)sender;

if ( contextMenuInfo != null ) {
var memb = (ComObjectMember)contextMenuInfo.SelectedItem.Tag;

string text = "";
if ( mi.Name == "CopyDefinition" ) {
text = memb.DefinitionText;
}
else if ( mi.Name == "CopyReturnType" ) {
text = memb.ReturnTypeText;
}
else if ( mi.Name == "CopyMemberName" ) {
text = memb.MemberName;
}
ClipboardSetText(text);
}
}

void Form_Resize(object sender, EventArgs e)
{
lsvProgID.Size = new System.Drawing.Size(spl.Panel1.ClientSize.Width, spl.Panel1.ClientSize.Height - lsvProgID.Top);
lsvMemb.Size = new System.Drawing.Size(spl.Panel2.ClientSize.Width, spl.Panel2.ClientSize.Height - lsvMemb.Top);
}

// 再取得処理
void ReloadProgIDs()
{
btnGetProgID.Enabled = false; // 処理に時間がかかるので操作を禁止しておく。※結局押したら意味ないかも
try {
allProgIDs = ProgIdInfo.GetProgIDs();
UpdateLsvProgID();
}
finally {
btnGetProgID.Enabled = true;
}
}

// 再取得 or フィルタ変更時の lsvProgID 更新
void UpdateLsvProgID()
{
lsvProgID.Items.Clear();
lsvProgID.BeginUpdate();
try {
foreach ( var t in allProgIDs ) {
string filterText = txtFilter.Text.Trim().ToLowerInvariant();

if ( filterText == "" ||
t.ProgID.ToLowerInvariant().Contains(filterText) ||
t.ValueOfCLSID.ToLowerInvariant().Contains(filterText) ) {
lsvProgID.Items.Add(MyNewListItem_LsvProgID(t));
}
}
}
finally {
lsvProgID.EndUpdate();
}
}

void ReloadLsvMemb(string comName)
{
List<ComObjectMember> comObjMembers;

btnGetMembers.Enabled = false;
try {
comObjMembers = ComObjectMember.GetMembers(comName);

lsvMemb.Items.Clear();
lsvMemb.BeginUpdate();
try {
foreach ( var t in comObjMembers ) {
lsvMemb.Items.Add(MyNewListItem_LsvMemb(t));
}
}
finally {
lsvMemb.EndUpdate();
}
}
finally {
btnGetMembers.Enabled = true;
}
}

ListViewItem MyNewListItem_LsvProgID(ProgIdInfo progId)
{
var itm = new ListViewItem(new string[]{progId.ProgID, progId.CLSID, progId.ValueOfCLSID});
itm.Tag = progId;
return itm;
}

ListViewItem MyNewListItem_LsvMemb(ComObjectMember memb)
{
var itm = new ListViewItem(new string[]{memb.MemberTypeText, memb.ReturnTypeText, memb.MemberName, memb.DefinitionText});
itm.Tag = memb;
return itm;
}

void LsvProgID_SelectedIndexChanged(object sender, EventArgs e)
{
if (lsvProgID.SelectedIndices.Count != 1) {return;}
int index = lsvProgID.SelectedIndices[0];
ProgIdInfo progIdInfo = (ProgIdInfo)(lsvProgID.Items[index].Tag);
txtProgID.Text = progIdInfo.ProgID;
}

void LsvProgID_ColumnClick(object sender, ColumnClickEventArgs e)
{
if ( e.Column >= 0 && e.Column < lsvProgID.Columns.Count ) {
lsvProgID.ListViewItemSorter = new ListViewItemComparer(e.Column);
}
}

public class ListViewItemComparer : IComparer
{
private int _column;
public ListViewItemComparer(int col) { _column = col; }

public int Compare(object obj1, object obj2)
{
string s1 = ((ListViewItem)obj1).SubItems[_column].Text;
string s2 = ((ListViewItem)obj2).SubItems[_column].Text;
return string.Compare(s1, s2, true); // 第3引数の true は、大文字小文字の差異を無視する指定(ignore case)
}
}

class MyListViewContextMenuInfo
{
private ListViewItem _SelectedItem;
private ListView _Sender;

public ListViewItem SelectedItem{get{return _SelectedItem;}}
public ListView Sender{get{return _Sender;}}

public MyListViewContextMenuInfo(ListView sender, ListViewItem selectedItem)
{
_Sender = sender;
_SelectedItem = selectedItem;
}
}

[STAThread]
static void Main(){
Application.Run(new MainForm());
}
}


機能追加

・テキストコピーできるようにする

→ 済み(右側のみ) 右クリックで選択項目コピー可能

・ListView上の検索(filtering)

→ 済み


コンパイル方法

csc ^

/r:C:\Windows\assembly\GAC_MSIL\System.Management.Automation\1.0.0.0__31bf3856ad364e35\System.Management.Automation.dll ^
xxxx.cs


参考サイト