PowershellとUWSCの連携

UWSCPowershell関数は遅い。
恐らくアセンブリのロードに時間がかかるのだろう、、、。
二回目以降でも遅い。
なんとか早くしたいので、考えてみた。



そうだPowershellからUWSCを呼び出そう

Powershellを呼び出すのが遅いなら、PowershellからUWSCを呼べば良いじゃない!
ただ、UWSCに直接オブジェクトを注入する方法は思いつかなかったので、
Powershell <-> ie <-> UWSC
とする。
UWSCは特定のIEを監視して、EVALする、ということ。


p2u.uws

OPTION EXPLICIT

p2u.Run()


MODULE p2u
	DIM __regexp

	PROCEDURE p2u
		__regexp = CREATEOLEOBJ("VBScript.RegExp")
	FEND

	PROCEDURE Run()
		DIM __p2uIe = GETACTIVEOLEOBJ("InternetExplorer.Application", "Powershell uwsc bridge")
		DIM __p2u = __p2uIe.document.getElementsByTagName("input")
		DIM __p2uT = __p2u.Item(0), __p2uF = __p2u.Item(1), __i, __res, __fuki = FALSE

		TRY
			COM_ERR_IGN
			WHILE !COM_ERR_FLG
				IFB LENGTH(__p2uT.value) THEN
					__res = ""
					TRY
						__p2u = GetEvalStrings(__p2uT.value)
						FOR __i = 0 TO LENGTH(__p2u) - 1
							IF LENGTH(__res) THEN __res = __res + "<#CR>"
							__res = __res + EVAL(__p2u[__i])
						NEXT
					EXCEPT
						__res = TRY_ERRMSG
					ENDTRY
					IF LENGTH(__res) = 0 THEN __res = " "
					__p2uF.value = __res
					__p2uT.value = ""
				ELSEIF G_MOUSE_X = 0 THEN
					IFB !__fuki THEN
						FUKIDASI("p2u is running.")
						__fuki = TRUE
					ENDIF
				ELSE
					IFB __fuki THEN
						FUKIDASI()
						__fuki = FALSE
					ENDIF
					SLEEP(0.1)
				ENDIF
			WEND
			COM_ERR_RET
		EXCEPT
			// nop
		ENDTRY

		// 終了処理
		TRY
			__p2uIe.Quit()
		EXCEPT
			// nop
		ENDTRY
	FEND

	FUNCTION GetEvalStrings(code)
		RESULT = SPLIT(code, "<#CR>")
		DIM i, line, m = LENGTH(RESULT), ms, j
		FOR i = 0 TO m - 1
			// 行結合を考慮する
			IFB COPY(RESULT[i], LENGTH(RESULT[i])) = "_" AND i + 1 < m THEN
				RESULT[i] = COPY(RESULT[i], 1, LENGTH(RESULT[i]) - 1) + RESULT[i + 1]
				RESULT[i + 1] = ""
			ENDIF
			// マルチステートメントを考慮する
			line = TRIM(RESULT[i])
			j = 0
			ms = SAFEARRAY(0, 0)
			WHILE LENGTH(line)
				ms[j] = TOKEN(";", line, TRUE, TRUE)
				j = j + 1
				RESIZE(ms, j)
			WEND
			IFB j > 1 THEN
				RESIZE(ms, j - 1)
				InsertArray(RESULT, i, ms)
			ENDIF

			line = TRIM(RESULT[i])
			// OPTION指定は捨てる
			IF RegExp(line, "^OPTION\s+[a-zA-Z]+", TRUE) THEN RESULT[i] = ""
			// DIMで始まる行は、DIM捨ててカンマ区切りで=を:=に変更して
			IF RegExp(line, "^DIM\s+\S+", TRUE) THEN ReconstDim(RESULT, i)
		NEXT
	FEND

	FUNCTION RegExp(s, p, t=FALSE, g=FALSE, i=TRUE, m=FALSE)
		__regexp.Pattern = p
		__regexp.Global = g
		__regexp.IgnoreCase = i
		__regexp.Multiline = m
		IFB t THEN
			RESULT = __regexp.Test(s)
		ELSE
			RESULT = __regexp.Execute(s)
		ENDIF
	FEND
	FUNCTION RepExp(s, p, t=EMPTY, g=FALSE, i=TRUE, m=FALSE)
		__regexp.Pattern = p
		__regexp.Global = g
		__regexp.IgnoreCase = i
		__regexp.Multiline = m
		RESULT = __regexp.Replace(s, t)
	FEND

	PROCEDURE ReconstDim(var codes, index)
		DIM line = TRIM(codes[index])
		IF !RegExp(line, "^DIM\s+\S+", TRUE) THEN EXIT
		line = TRIM(COPY(line, 4))
		DIM ms = SAFEARRAY(0, 0), i = 0, off
		WHILE LENGTH(line)
			ms[i] = TOKEN(",", line, TRUE, TRUE)
			IFB RegExp(ms[i], "=", TRUE) THEN
				ms[i] = RepExp(ms[i], "=", ":=")
				i = i + 1
				RESIZE(ms, i)
			ELSE
				off = RegExp(ms[i], "\[[^\]]+\]", FALSE, TRUE)
				ms[i] = RepExp(ms[i], "\[.*")
				IFB off.Count > 0 THEN
					ms[i] = ms[i] + ":=SAFEARRAY(0, " + COPY(off.Item(0).Value, 2, LENGTH(off.Item(0).Value) - 2)
					IFB off.Count = 1 THEN
						ms[i] = ms[i] + ")"
					ELSE
						ms[i] = ms[i] + ", 0, " + COPY(off.Item(1).Value, 2, LENGTH(off.Item(1).Value) - 2) + ")"
					ENDIF
				ELSE
					ms[i] = ""
				ENDIF
			ENDIF
		WEND
		InsertArray(codes, index, ms)
	FEND

	PROCEDURE InsertArray(var src, idx, ins)
		// srcのidx番目にinsを入れる。idx番目は上書きする
		DIM im = LENGTH(ins) - 1, i
		IF im < 0 THEN EXIT
		IFB im >= 0 THEN
			DIM sm = LENGTH(src)
			RESIZE(src, sm + im - 1)
			FOR i = sm - 1 TO idx + 1 STEP -1
				src[i + im] = src[i]
			NEXT
		ENDIF
		FOR i = 0 TO im
			src[i + idx] = ins[i]
		NEXT
	FEND

ENDMODULE


で、Powershell側はこんな感じ。

$p2u = New-Object PSObject
$p2u | Add-Member NoteProperty "ie" (New-Object -ComObject InternetExplorer.Application)
$p2u.ie.Navigate("about:blank")
$p2u.ie.Document.title = "Powershell uwsc bridge"
#$p2u.ie.Visible = $true
$p2u | Add-Member NoteProperty "to" $p2u.ie.Document.createElement("input")
$p2u.to.type = "hidden"
$p2u.to.name = "_powershell2uwsc"
[VOID]$p2u.ie.Document.body.appendChild($p2u.to)
$p2u | Add-Member NoteProperty "from" $p2u.ie.Document.createElement("input")
$p2u.from.type = "hidden"
$p2u.from.name = "_uwsc2powershell"
[VOID]$p2u.ie.Document.body.appendChild($p2u.from)

$p2u | Add-Member ScriptMethod "Run" {
	$this.from.value = ""
	if($args.length -gt 0) {
		$this.to.value = $args[0]
		$count = 6000
		if($args.length -gt 1) { $count = $args[1] }
		if($count -gt 0) {
			while(($this.from.value.length -eq $null) -and ($count -gt 0)) {
				Start-Sleep -Milliseconds 100
				$count--
			}
			if($this.from.value.length -gt 0) {
				"Run: " + $this.from.value
				$this.from.value = ""
			} else {
				"Run timeout!"
			}
		}
	}
}

.\p2u.uws

使い方

PowershellからRunメソッドにUWSCにEVALさせる文字列を渡すだけ。
上の例なら

$p2u.Run("MSGBOX(""aaa"")")

といったところ。
UWSC側で改行SPLITしてるので、複数行も可能。

$p2u.Run(@"
a := "aaa"
MSGBOX(a)
"@)


x64のPowershell&IEでもOKだとは知らなかった、、、。


UWSC側のスクリプトを読むと判りますが、DIM文を解釈しようとしてたりします(不完全)
拡張していけば、掲示板のスクリプトPowershellにペーストして実行、なんてこともできそうですね。


終了する場合は、PowershellIEオブジェクトを閉じる想定。

$p2u.ie.Quit()