1. kob58im

    No comment

    kob58im
Changes in body
Source | HTML | Preview
@@ -1,414 +1,552 @@
[COMオブジェクトのメンバー一覧を取得する (PowerShell)](https://qiita.com/kob58im/items/ea1ca69b9f898845a71f)
を応用してブラウザつくってみた。
~~※oleview.exeを持ってればいらない説…(まだ使ってないのでわからない)~~
ちなみにCOMオブジェクトでない普通の.Netのクラス(System.IO.Fileとか)であれば、
[C#リフレクションTIPS 55連発](https://qiita.com/gushwell/items/91436bd1871586f6e663)
が参考になります。
-# つくってみた(キャプチャ
+# 画面キャプチャ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/6a69cd34-fa1f-b549-9a63-ec203c0e92a9.png)
# ソースコード
+機能追加にあたってリファクタリングしまくり。。
+
```csharp
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[]{""}); // 規定値を取得する。 // レジスアクセスでやってもよい
+ object t = psObj.Methods["GetValue"].Invoke(new object[]{""}); // 規定値を取得する。 // レジストリアクセスでやってもよい
return (t??"").ToString(); // Microsoft PenInputPanel ControlがなぜかProgIdの値が空欄なので、処置しないと例外発生する
}
}
-class MainForm:Form
+public class ComObjectMember
{
- Button btnGetProgID;
- Button btnGetMembers;
-// CheckBox chkFilter;
- TextBox txtFilter;
- SplitContainer spl;
- TextBox txtProgID;
- ListView lsvProgID;
- ListView lsvMemb;
+ private PSObject _psObj;
+ private string _MemberTypeText;
+ private string _ReturnTypeText;
+ private string _MemberName;
+ private string _DefinitionText;
- List<ProgIdInfo> allProgIDs; // filter処理を入れたいので、ListViewに直接持たせるのはやめた。
+ private static Regex r = new Regex(@"^([^ ]+)");
-
- static Collection<PSObject> GetMembers(string comObjectName)
+ 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"
};
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("Name", 130, 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)
{
- Collection<PSObject> psObjs;
+ List<ComObjectMember> comObjMembers;
btnGetMembers.Enabled = false;
try {
- psObjs = GetMembers(comName);
+ comObjMembers = ComObjectMember.GetMembers(comName);
lsvMemb.Items.Clear();
lsvMemb.BeginUpdate();
try {
- foreach ( var t in psObjs ) {
+ 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(PSObject psObj)
+ ListViewItem MyNewListItem_LsvMemb(ComObjectMember memb)
{
- ListViewItem itm;
- 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 ) {
- string memberType = (psObj.Members["MemberType"].Value).ToString(); // PSMemberTypes (Method / Property など)
- string returnType = m.Groups[1].Value;
- string methodName = psObj.Members["Name"].Value.ToString();
- string definition = psObj.Members["Definition"].Value.ToString();
- itm = new ListViewItem(new string[]{memberType, returnType , methodName, definition});
- }
- else {
- // 想定外の形式の場合は、とりあえずToString()して表示しておく
- itm = new ListViewItem(new string[]{"", "", "", psObj.ToString()});
- }
-
+ 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());
}
}
-
-
```
-判ったこと:
-PSObjectのMembersプロパティやMethodsプロパティは、COMオブジェクトの情報は拾えないようである。。
-機能追加したいこと:
-・テキストコピーできるようにする
-・ListView上の検索(filtering) → 済み
+
+# 機能追加
+・テキストコピーできるようにする
+→ 済み(右側のみ) 右クリックで選択項目コピー可能
+・ListView上の検索(filtering)
+→ 済み
# コンパイル方法
```
csc ^
/r:C:\Windows\assembly\GAC_MSIL\System.Management.Automation\1.0.0.0__31bf3856ad364e35\System.Management.Automation.dll ^
xxxx.cs
```
# 参考サイト
- [C# 上で PowerShell スクリプトを実行 (++C++; //未確認飛行 C)](https://ufcpp.net/study/powershell/interop.html)
- [PSObjectクラス (Microsoft) ](https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.psobject?view=pscore-6.2.0)
+
+- [ListView(Details)のカラムヘッダ上でコンテキストメニューを出したくないっ!](https://qiita.com/Toraja/items/51ffc5cbfa0b9f8e154f)