変奏現実

パソコンやMMORPGのことなどを思いつくまま・・・記載されている会社名・製品名・システム名などは、各社の商標、または登録商標です。

この画面は、簡易表示です

PowerShell(V7)

[C#(v7) + Power Shell(v7)]ActiveXObjectのメンバーの情報を取得したい

64ビット環境では、

Activator.CreateInstance(t)

の結果が全てラッパーなクラス(System.__ComObject)になるので、progIdのインタフェースの内容を調べる方法が無い。(らしい

Power Shell でActiveXObjectの変数を作ると

> [System.Environment]::Is64BitProcess
True ※ 64ビット版で動作中(らしい
> $ac = New-Object -ComObject ADODB.Connection
> Get-Member -InputObject $ac > ADODB.Connection.txt

Get-Member でインタフェースの内容がテキストで出力される。

   TypeName: System.__ComObject#{00001550-0000-0010-8000-00aa006d2ea4}
Name              MemberType Definition                                        
----              ---------- ----------                                        
BeginTrans        Method     int BeginTrans ()                                 
Cancel            Method     void Cancel ()                                    
Close             Method     void Close ()                                     
CommitTrans       Method     void CommitTrans ()                               
Execute           Method     _Recordset Execute (string, Variant, int)         
Open              Method     void Open (string, string, string, int)           
OpenSchema        Method     _Recordset OpenSchema (SchemaEnum, Variant, Var...
RollbackTrans     Method     void RollbackTrans ()                             
Attributes        Property   int Attributes () {get} {set}                     
CommandTimeout    Property   int CommandTimeout () {get} {set}                 
ConnectionString  Property   string ConnectionString () {get} {set}            
ConnectionTimeout Property   int ConnectionTimeout () {get} {set}              
CursorLocation    Property   CursorLocationEnum CursorLocation () {get} {set}  
DefaultDatabase   Property   string DefaultDatabase () {get} {set}             
Errors            Property   Errors Errors () {get}                            
IsolationLevel    Property   IsolationLevelEnum IsolationLevel () {get} {set}  
Mode              Property   ConnectModeEnum Mode () {get} {set}               
Properties        Property   Properties Properties () {get}                    
Provider          Property   string Provider () {get} {set}                    
State             Property   int State () {get}                                
Version           Property   string Version () {get}                           

参考:https://docs.microsoft.com/ja-jp/powershell/scripting/samples/creating-.net-and-com-objects–new-object-?view=powershell-7.2&viewFallbackFrom=powershell-6

の「WScript.Shell によるデスクトップ ショートカットの作成」の項

この記事、作成したショートカットの内容を保存するにはSaveメソッドを使います。という内容。

統合開発環境(IDE)でソースを書けば、自動的にメソッドの候補がリストアップされるのが当たり前だけど、PowerShellの窓は窓だけそんなモノはない。

そこで窓の中でメソッドを調べる方法を優しく伝授したかったらしい。(良い人じゃないですか!

$lnk | Get-Member

さて、C#からPowerShellを呼び出すには、

普通にWindows Storeからインストすると、コマンドラインのみ許可なインストに。

普通にダウンロードしてPowerShellをインストール。

おや? InvalidProgramException ex

どうやら、32ビットのDLLらしい。

仕方なく、PowerShellVer7の64ビット版をインストール。

やっと動き出す。

using (var invoker = new RunspaceInvoke())
{
    string source = @"$ac = New-Object -ComObjec " + progID + @" ; Get-Member -InputObject $ac";
    var results = invoker.Invoke(source, new object[] { });
    foreach (var result in results)    {
        Console.Write(result);
    }
}

古いVer5ではInvokeしまくればよかったのに、

今のVer7では

// PowerShellオブジェクトを作成
PowerShell ps = PowerShell.Create();
// 実行するコマンドを追加
ps.AddCommand("New-Object")
    .AddParameter("-ComObject", progId);
// コマンドを呼び出す
var results1 = ps.Invoke();
//ps.AddCommand("Get-Member -InputObject $ac");
ps.AddStatement().AddCommand("Get-Member")
    .AddParameter("-InputObject", results1);
// コマンドを呼び出す
var results2 = ps.Invoke();

かと思ったけど、AddScriptを使えばPowerShellの窓で打った内容をそのまま使えるらしい。

PowerShell ps = PowerShell.Create();
// 実行するコマンドを追加
ps.AddScript("$ac = New-Object -ComObject ADODB.Connection");
ps.AddScript("Get-Member -InputObject $ac");
var results = ps.Invoke();

しかし、参照するDLLが多い。

吐き出される InvalidProgramException を読みつつ参照DLLを追加していくと

今回使用分だけなのに、このザマ。

しかも、ローカルにコピーを指定しても、PowerShellをアンスコすると動かなくなる。

あげくに、結果がSystem.__ComObjectの内容だった。

★ADODB.Connection
☆start
System.__ComObject
void Add(psobject item), void ICollection[psobject].Add(psobject item), int IList.Add(System.Object value)
void Clear(), void ICollection[psobject].Clear(), void IList.Clear()
bool Contains(psobject item), bool ICollection[psobject].Contains(psobject item), bool IList.Contains(System.Object value)
void CopyTo(psobject[] array, int index), void ICollection[psobject].CopyTo(psobject[] array, int arrayIndex), void ICollection.CopyTo(array array, int index)
bool Equals(System.Object obj)
System.Collections.Generic.IEnumerator[psobject] GetEnumerator(), System.Collections.Generic.IEnumerator[psobject] IEnumerable[psobject].GetEnumerator(), System.Collections.IEnumerator IEnumerable.GetEnumerator()
int GetHashCode()
type GetType()
int IndexOf(psobject item), int IList[psobject].IndexOf(psobject item), int IList.IndexOf(System.Object value)
void Insert(int index, psobject item), void IList[psobject].Insert(int index, psobject item), void IList.Insert(int index, System.Object value)
bool Remove(psobject item), bool ICollection[psobject].Remove(psobject item), void IList.Remove(System.Object value)
void RemoveAt(int index), void IList[psobject].RemoveAt(int index), void IList.RemoveAt(int index)
string ToString()
psobject Item(int index) {get;set;}
int Count {get;}
bool IsFixedSize {get;}
bool IsReadOnly {get;}
bool IsSynchronized {get;}
System.Object SyncRoot {get;}
☆end

もうダメか?と思ったけど、先のaddScriptを使う方はうまくいった。

一旦Get-Memberの結果をC#に引き渡した時にラップされすぎしたのかもしれない。

★ADODB.Connection
☆start
int BeginTrans ()
void Cancel ()
void Close ()
void CommitTrans ()
_Recordset Execute (string CommandText, Variant RecordsAffected, int Options)
void Open (string ConnectionString, string UserID, string Password, int Options)
_Recordset OpenSchema (SchemaEnum Schema, Variant Restrictions, Variant SchemaID)
void RollbackTrans ()
int Attributes () {get} {set}
int CommandTimeout () {get} {set}
string ConnectionString () {get} {set}
int ConnectionTimeout () {get} {set}
CursorLocationEnum CursorLocation () {get} {set}
string DefaultDatabase () {get} {set}
Errors Errors () {get}
IsolationLevelEnum IsolationLevel () {get} {set}
ConnectModeEnum Mode () {get} {set}
Properties Properties () {get}
string Provider () {get} {set}
int State () {get}
string Version () {get}
☆end

Errorsとかコレクションの場合はCountプロパティがあればほぼ間違いない。

> Get-Member -InputObject $ac.Errors
   TypeName: System.__ComObject#{00000501-0000-0010-8000-00aa006d2ea4}
Name    MemberType            Definition
----    ----------            ----------
Clear   Method                void Clear ()
Refresh Method                void Refresh ()
Item    ParameterizedProperty Error Item (Variant) {get}
Count   Property              int Count () {get}

> Get-Member -InputObject $ac.Properties
   TypeName: System.__ComObject#{00000504-0000-0010-8000-00aa006d2ea4}
Name    MemberType            Definition
----    ----------            ----------
Refresh Method                void Refresh ()
Item    ParameterizedProperty Property Item (Variant) {get}
Count   Property              int Count () {get}

という感じでネストすればよさそう。

意外だったのが、ADODB.Fieldがレジストリィ未登録らしく失敗するので、これは特定のコマンドを叩いて調べることにする。

もう少しテンプレの作り方を工夫した方がよさそうだ。

いや、そもそも、GetMemberBinderをどうにかできれば、PowerShellを呼ばなくてもいいんだけどね。

internal class PowerShellExec
{
    public static void MakeActiveXTemplateJDFile(string docRoot, string[] classNames)
    {
        //  ActiveXObjectのテンプレ作成
        foreach (string className in classNames)
        {
            MakeActiveXObjectJsFile(docRoot, className);
        }
    }
    //  ActiveXObjectのテンプレ作成
    public static void MakeActiveXObjectJsFile(string docRoot, string className)
    {
        MemInfo[]? result = PowerShellExecute(className);
        // 複数行の場合がある
        className = className.Split("\r\n")[0];
        if (result == null)
            return;
        foreach (MemInfo p0 in result)
        {
            //Console.WriteLine(p0.ToString());
        }
        MemInfo[] p = result.Where(x => x.memberType == PSMemberTypes.Property && x.child == null).ToArray();
        MemInfo[] c = result.Where(x => x.memberType == PSMemberTypes.Property && x.child != null).ToArray();
        MemInfo[] m = result.Where(x => x.memberType == PSMemberTypes.Method).ToArray();
        //
        string temp = @"
//  " + className + @" クラス
class " + className.Replace(".", "_") + @" extends ActiveXObject {
    constructor() {
        // とりあえず定番
        super();
        this.className = this.constructor.name.replace('_', '.');
        // プロパティ
        // 名前リストからプロパティを作成
        this.propertyNames = [" + string.Join(", ", p.Select(x => "'" + x.name + "'")) + @"];
        this.makeProperties(this.propertyNames);
        // プロパティ(コレクション)
        // 名前リストからダミーのメソッド(コレクション)を作成
        this.propertyCollections = {
" + string.Join(@",
",
    c.Select(x1 => {
        MemInfo[] cp = x1.child.Where(x2 => x2.memberType == PSMemberTypes.Property).ToArray();
        MemInfo[] cm = x1.child.Where(x2 => x2.memberType == PSMemberTypes.Method).ToArray();
        return "            '" + x1.name + @"': {
" + "                'properties': [" + string.Join(", ", cp.Select(x3 => "'" + x3.name + "'")) + @"],
                'methods': [" + string.Join(", ", cm.Select(x3 => "'" + x3.name + "'")) + @"],
                'events': []
            }";
    }))
+ @"
        };
        this.makePropertiesCollection(this.propertyCollections);
        // メソッド
        // 名前リストからダミーのメソッドを作成
        this.methodNames = [" + string.Join(", ", m.Select(x => "'" + x.name + "'")) + @"];
        this.makeMethods(this.methodNames);
        // イベント
        // 名前リストからダミーのメソッドを作成
        this.eventNames = [];
        this.makeEvents(this.eventNames);
    }
}
";
        //
        string path = docRoot + className.Replace(".","_") + ".js";
        if (!File.Exists(path))
        {
            File.WriteAllText(path, temp);
        }
    }
    // 
    public static MemInfo[]? PowerShellExecute(string progInfo)
    {
        string progId="";
        try
        {
            List<MemInfo> rc = new();
            // PowerShellオブジェクトを作成
            // Powershell.Create("get-process").Invoke(); はもうできない。
            using (PowerShell ps = PowerShell.Create())
            {
                // 実行するコマンドを追加
                // $ac = New-Object -ComObject ADODB.Connection
                // Get-Member -InputObject $ac
                string[] infoA = progInfo.Split("\r\n");
                progId = infoA[0];
                //
                string[] cmdLines = new string[]{
                    "$ac = New-Object -ComObject " + progId,
                    "Get-Member -InputObject $ac"
                };
                // 
                if (infoA.Length > 1)
                {
                    // 先頭はprogId
                    progId = infoA[0];
                    // 以降はPowerShellコマンド
                    cmdLines = infoA.Skip(1).ToArray();
                }
                //
                foreach (string cmdLine in cmdLines)
                {
                    ps.AddScript(cmdLine);
                }
                var results = ps.Invoke();
                //
                foreach (var result in results)
                {
                    rc.Add(new MemInfo(ps, "$ac.", result));
                }
                //Console.WriteLine("☆end");
            }
            return rc.ToArray();
        }
        catch (BadImageFormatException ex)
        {
            Console.WriteLine("★★★★★★ " + progId + ":" + ex.GetType().FullName + "\r\n" + "64ビット用のPowerShellのDLLを使用してください。\r\n" + ex.Message + "★★★★★★");
        }
        catch (InvalidProgramException ex)
        {
            Console.WriteLine("★★★★★★ " + progId + ":" + ex.GetType().FullName + "\r\n" + "\r\n" + ex.Message + "★★★★★★");
        }
        catch (Exception ex)
        {
            Console.WriteLine("★★★★★★ " + progId + ":" + ex.GetType().FullName + "\r\n" + ex.Message + "★★★★★★");
        }
        return null;
    }
}
//
public class MemInfo
{
    public string name;
    public string? type; 
    public string defition;
    public PSMemberTypes memberType;
    public List<MemInfo>? child = null;
    //
    public MemInfo(PowerShell ps, string preCmd, PSObject m)
    {
        dynamic a = m.ImmediateBaseObject;
        this.name = a.Name;
        //
        string[] definition = a.Definition.Split(" ");
        this.type = definition[0];
        //
        this.defition = a.Definition;
        this.memberType = a.MemberType;
        //
        if (a.MemberType == PSMemberTypes.Property)
        {
            // Countプロパティを持つプロパティはネスト
            ps.AddScript("Get-Member -InputObject " + preCmd + definition[1] + " -Name Count");
            var results = ps.Invoke();
            if (results.Count > 0)
            {
                List<MemInfo> rc = new();
                ps.AddScript("Get-Member -InputObject " + preCmd + definition[1]);
                results = ps.Invoke();
                foreach (var result in results)
                {
                    rc.Add(new MemInfo(ps, preCmd + definition[1] + ".", result));
                }
                this.child = rc;
            }
        }
    }
    //
    public override string ToString()
    {
        return this.defition;
    }
    //
    public static string Right(string str, int len)
    {
        if (len < 0)
        {
            throw new ArgumentException("引数'len'は0以上でなければなりません。");
        }
        if (str == null)
        {
            return "";
        }
        if (str.Length <= len)
        {
            return str;
        }
        return str.Substring(str.Length - len, len);
    }
}

これを

string[] classNames = new string[] {
    "ADODB.Connection",
    @"ADODB.Field  ※← レジストリィ未登録なので変数$acを使用する特定のPowerShellコマンドを指定して作成する。
$ac = New-Object -ComObject ADODB.Recordset
$ac.Fields.Append(""fld1"", 129, 20)
Get-Member -InputObject $ac.Fields(0)",
    "ADODB.Recordset"
};
//string[] classNames = new string[] { "ADODB.Connection" };
PowerShellExec.MakeActiveXTemplateJDFile(docRoot, classNames);

な感じっで呼ぶと

ADODB_Connection.js

ADODB_Field.js

ADODB_Recordset.js

の3ファイルができる。

※コマンドの変数を$acに固定しているのは、プロパティをネストしていく際に使用する変数が$acだから

// new ActiveXObject("xxx")用
class ActiveXObject {
    constructor(className, json)
    {
        this.domain       = "localhost"; ←適宜修正
        this.port         = "8000"; ←適宜修正
        this.className    = className;
        this.objectID     = null;
        // 派生クラスの場合は再帰してしまうので、処理を省略
        if(typeof(className) !== 'undefined')
        {
            // classNameから当該クラスのJavaScript側クラスのオブジェクトを作成
            let cf = new Function('className', `return new ${className.replace('.', '_')}()`);
            let obj = undefined;
            try {
                obj = cf(className);
            } catch (ex) {
                throw new Error(`★new ActiveXObject("${className}")未定義クラス`);
            }
            // 初期設定値(json)があれば、設定する
            if (typeof (json) !== 'undefined') {
                obj['objectID'] = json['objectID'];
                obj.__setup__(json);
            }
            else
            // 無ければ, C#のサーバに問い合わせる。
            {
                // サーバー側にnewしたことを通知
                // obj.init();
                // 放置してもサーバ側のオブジェクトは解放されないので、オブジェクトが不要になったら、適宜termメソッドを呼び出す事
                // this.term();
            }
            return obj; // new の戻り値
        }
    }
    // 設定する
    __setup__(json) {
        // プロパティにコピー
        for (let key of this.propertyNames) {
            let jj = json[key]
            if (typeof(jj) !== "undefined") {
                this[`_${key}`] = json[key];
            }
        }
        // プロパティ(コレクション)にコピー
        for (let key in this.propertyCollections) {
            let jj = json[key]
            if (typeof(jj) !== "undefined") {
                if (typeof (this[`_${key}`]) === "undefined" || this[`_${key}`] === null) {
                    this[`_${key}`] = {};
                }
                for (let idx in jj) {
                    let k = jj[idx].Name;
                    this[`_${key}`][`${k}`] = jj[idx];
                }
                // this[key]['Item'] = this[key]
            }
        }
    }
    // サーバー側にnewしたことを通知
    async init() {
        let json = await ActiveXObject.staticSendMessage('POST', this.domain, this.port, `ActiveXObject/${this.className}`);
        ActiveXObject.staticInit(this, json);
    }
    // オブジェクトの解放
    async term() {
        return await ActiveXObject.staticSendMessage('DELETE', this.domain, this.port, `ActiveXObject/${this.objectID}`);
    }
    // サーバー側にnewしたことを通知
    static staticInit(obj, json)
    {
        // 必須
        if (typeof (json["objectID"]) === 'Nothing')
        {
            alert("objectIDが取得できません。");
        }
        obj.objectID = json["objectID"];
        if (typeof (obj.onobjectID) !== 'undefined')
        {
            obj.onobjectID();
        }
        obj.SetProperties(json['activeXobject']);
        //
    }
    // 送信
    static staticSendMessage(method, domain, port, page, data)
    {
        let promise = new Promise( (resolve, reject) => {
            try {
                let parserFunction = this.parserFunction;
                let xhr = new XMLHttpRequest();
                let url = encodeURI(`http://${domain}:${port}/${page}`);
                xhr.open(method, url, true);
                xhr.responseType = 'text';
                xhr.send(this.staticEncodeHTMLForm(data));
                xhr.onload = function(e) {
                    if (xhr.readyState == 4) {
                        if (xhr.status == 200 ) {
                            try {
                                let json = xhr.response;
                                json = JSON.parse(json, parserFunction);
                                resolve(json);
                                return;
                            } catch (ex) {
                                console.log(`'${ex.toString()}'\n url:http://${domain}:${port}/${url}\n json:'${xhr.response}'`);
                            }
                        }
                    }
                };
            }
            catch (ex) {
                reject(ex);
                return;
            }
        });
        return promise;
    }
    // データをBODYの形式に変換
    static staticEncodeHTMLForm(data) {
        if (typeof(data) === 'undefined' || data === null)
        {
            return null;
        }
        let a = [];
        for (let aa of data) {
            a.push(aa);
        }
        return "params=" + JSON.stringify(a);
    }
    //
    static staticMakeUrlParameters(arg) {
        let rc = [];
        for (let idx = 1; idx <= arg.length; idx++) {
            rc.add(`p${idx}=JSON.stringify(${arg[idx - 1]})`);
        }
        return rc.join('&');
    }
    //
    static staticParserFunction(k,v)
    {
        try
        {
            if (typeof(v) === "string" && ((v.indexOf("function") === 0) || (v.indexOf("async") === 0)))
            {
                return eval(`(${v})`);
            }
            else
            {
                return v;
            }
        } catch (ex) {
            console.log(ex.toString());
        }
    }
    //
    /**
     * 名前リストからプロパティを作成
     * 
     * @param {String[]} propertyNames   名前リスト
     */
    makeProperties(propertyNames) {
        for (let propName of propertyNames) {
            Object.defineProperty(this, propName, {
                get() {
                    return this[`_${propName}`];
                },
                set(value) {
                    this[`_${propName}`] = value;
                }
            });
        }
    }
    /**
     * 名前リストからプロパティ(コレクション)を作成
     * 
     * @param {String[]} propertyCollectionNames   名前リスト
     */
    makePropertiesCollection(propertyCollections) {
        for (let collectionName in propertyCollections) {
            if (typeof (this[collectionName]) === "undefined") {
                this[collectionName] = {};
            }
            let co = this[collectionName];
            // コレクション本体
            Object.defineProperty(this, collectionName, {
                get() {
                    return this[`_${collectionName}`];
                },
                set(value) {
                    this[`_${collectionName}`] = value;
                }
            });
            // コレクションの設定内容
            let collection = propertyCollections[collectionName];
            // コレクションのプロパティ
            // property
            for (let propertyName of collection.properties) {
                Object.defineProperty(co, propertyName, {
                    get() {
                        return co[`_${propertyName}`];
                    },
                    set(value) {
                        co[`_${propertyName}`] = value;
                    }
                });
            }
            // コレクションのメソッド
            for (let methodName of collection.methods) {
                if (typeof (co[methodName]) === "undefined")
                {
                    co[methodName] = function () {
                        let args = arguments;
                        if (this.objectID == null) {
                            this.onobjectID = async function () {
                                let rc = await co[methodName].apply(co, args);
                                resolve(rc);
                                return;
                            }
                        } else {
                            let path = `ActiveXObject/${this.objectID}/${collectionName}/${methodName}`;
                            return new Promise(async (resolve, reject) => {
                                let json = await ActiveXObject.staticSendMessage('PUT', this.domain, this.port, path, args);
                                // エラーがあれば返す。
                                if (typeof (json['error']) !== 'undefined') {
                                    reject(json['error']);
                                    return;
                                }
                                //
                                this.SetProperties(json['activeXObject']);
                                //
                                let rc = json['returnValue'];
                                if (rc != null && rc["__constructor_name__"]) {
                                    let className = rc["__constructor_name__"];
                                    delete rc["__constructor_name__"];
                                    let obj = new ActiveXObject(className, rc);
                                    resolve(obj);
                                    return;
                                }
                                resolve(rc);
                                return;
                            });
                        }
                    };
                }
            }
            // event 未定
            //
        }
    }
    /**
     * 名前リストからメソッドを作成
     * 
     * @param {String[]} methodNames   名前リスト
     */
    makeMethods(methodNames) {
        for (let methodName of methodNames) {
            this[methodName] = function () {
                let args = arguments;
                if (this.objectID == null) {
                    return new Promise(async (resolve, reject) => {
                        this.onobjectID = async function () {
                            let rc = await this[methodName].apply(this, args);
                            resolve(rc);
                            return;
                        }
                    });
                } else {
                    let path = `ActiveXObject/${this.objectID}/${methodName}`;
                    return new Promise(async (resolve, reject) => {
                        let json = await ActiveXObject.staticSendMessage('PUT', this.domain, this.port, path, args);
                        // エラーがあれば返す。
                        if (typeof (json['error']) !== 'undefined') {
                            reject(json['error']);
                            return;
                        }
                        //
                        this.SetProperties(json['activeXObject']);
                        //
                        let rc = json['returnValue'];
                        if (rc != null && rc["__constructor_name__"]) {
                            let className = rc["__constructor_name__"];
                            delete rc["__constructor_name__"];
                            let obj = new ActiveXObject(className, rc);
                            resolve(obj);
                            return;
                        }
                        resolve(rc);
                        return;
                    });
                }
            };
        }
    }
    /**
     * 名前リストからイベントを作成
     * 
     * @param {any} eventNames
     * @param {any} staticMakeUrlParameters
     */
    makeEvents(eventNames) {
        // イベントは、逐次作成
    }
    //
    staticMakeUrlParameters(args) {
        if (args.length == 0) {
            return "";
        }
        let rc = [];
        for (let [arg, index] of args) {
            rc.push(`param${index}:${arg}`);
        }
        return rc.join("&");
    }

    // SendMessageの戻り値の['activeXobject']からプロパティを設定
    // 更新するプロパティがあれば処理する
    SetProperties(jsonReturn) {
        // 何もなければ何もしない
        if (typeof (jsonReturn) === 'undefined') {
            return;
        }
        if (jsonReturn === null) {
            return;
        }
        //
        delete jsonReturn["__constructor_name__"];
        this.__setup__(jsonReturn);
    }
}



top