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);
}
}