1. kob58im

    No comment

    kob58im
Changes in body
Source | HTML | Preview
@@ -1,413 +1,414 @@
[COMオブジェクトのメンバー一覧を取得する (PowerShell)](https://qiita.com/kob58im/items/ea1ca69b9f898845a71f)
を応用してブラウザつくってみた。
-※oleview.exeを持ってればいらない説…(まだ使ってないのでわからない)
+~~※oleview.exeを持ってればいらない説…(まだ使ってないのでわからない)~~
ちなみにCOMオブジェクトでない普通の.Netのクラス(System.IO.Fileとか)であれば、
[C#リフレクションTIPS 55連発](https://qiita.com/gushwell/items/91436bd1871586f6e663)
が参考になります。
# つくってみた(キャプチャ)
-![comobjbrowser.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/74fe8480-6a6e-700c-51dc-5f7497a1dc04.png)
+![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等の書き込み系のメソッドを使わないように。)
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の値が空欄なので、処置しないと例外発生する
}
}
class MainForm:Form
{
Button btnGetProgID;
Button btnGetMembers;
// CheckBox chkFilter;
TextBox txtFilter;
SplitContainer spl;
TextBox txtProgID;
ListView lsvProgID;
ListView lsvMemb;
List<ProgIdInfo> allProgIDs; // filter処理を入れたいので、ListViewに直接持たせるのはやめた。
static Collection<PSObject> GetMembers(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 を返しておく
}
}
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("Definition", 350, HorizontalAlignment.Left);
spl.Panel2.Controls.Add(lsvMemb);
////// 右 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 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;
btnGetMembers.Enabled = false;
try {
psObjs = GetMembers(comName);
lsvMemb.Items.Clear();
lsvMemb.BeginUpdate();
try {
foreach ( var t in psObjs ) {
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 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()});
}
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)
}
}
[STAThread]
static void Main(){
Application.Run(new MainForm());
}
}
```
判ったこと:
PSObjectのMembersプロパティやMethodsプロパティは、COMオブジェクトの情報は拾えないようである。。
機能追加したいこと:
・テキストコピーできるようにする
・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)