4
4

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-08-27

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

参考サイト

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4