VBAからGDI+を使う資料集
inet上の達人の方々から御教示いただいたコードを少しアレンジさせてもらっています(切り貼りとも言います)。
  1. ホーム
  2. Other
  3. execjswithvba


Excel/VBAからJavaScriptのコードを実行してみる

ExcelからJavaScriptのコードを実行して結果が受け取れたら便利かと思ってやってみました。
既存のページのJavaScriptを実行という記事を探していらっしゃる方には的外れです
Webページを1からExcel VBAで構築してやり、JavaScriptも実行させようという内容です。

ScriptControlという選択肢がありますが、これは64bit版は無いそうで、自宅の環境では使えません。
また、window.execScriptというIEの方言は、IE11からは廃止されたそうです。
残された選択肢のdocment.script.setTimeoutを試してみました。
これも2014年末のWindows Updateで縛りがきつくなった様です。<KB3025390>
確かに偽装Windowの表示等に使えそうな機能ではあります。
代替手段も考えてみました。


'☆ 標準モジュール
Private Declare Sub Sleep Lib "kernel32" (ByVal ms As Long)

Sub execJScode()
    Dim objIE As InternetExplorer
    Dim doc As IHTMLDocument
    Dim myHeader As IHTMLElement
    Dim myBody As IHTMLElement
    Dim newElement As IHTMLElement
    Dim mybutton As IHTMLButtonElement
    Dim myScript As String
    Dim myResult As IHTMLElement
    Dim myResultArray As IHTMLUListElement
    Dim myListItem As IHTMLLIElement
    Dim myArray() As String
    Dim i As Long
     
    Set objIE = New InternetExplorer
    '可視、Trueで見えるようにします。
    objIE.Visible = True
    
    'これを入れないと、DOMの変更が反映されない
    '入れると、コードからの、doc.script.setTimeoutがセキィリティに引っかかる場合がある
    objIE.navigate "about:blank"
    
    Set doc = objIE.document
    
    doc.write "<html><head></head><body></body></html>"
    Set myHeader = doc.getElementsByTagName("head")(0)
    Set myBody = doc.getElementsByTagName("body")(0)
    Set newElement = doc.createElement("SCRIPT")
    newElement.Type = "text/javascript"
    myScript = ActiveSheet.Shapes("TextBox 1").TextFrame.Characters.Text
    If myScript = "" Then Exit Sub
    newElement.Text = myScript
    myHeader.appendChild newElement
     
    'アクセスが拒否されましたというエラーになる
    'doc.script.setTimeout "myjscode()", 10
    
    '"about:blank"とは別ドメインからの実行とみなされるらしい。
    'かつては実行出来たと思うがセキュリティが厳しくなったものか。
    'イントラネットのセキュリティレベルを低にしてみたがNG
    'KB3025390が関係する 2014/12現在
    
    'これは苦肉の策
     Set mybutton = doc.createElement("button")
     mybutton.setAttribute "onclick", "myjscode();"
'     mybutton.innerText = "exec myjscode" '無くても可
     myBody.appendChild mybutton
     mybutton.Click
     myBody.RemoveChild mybutton  'オートメーションエラーになる時があるが原因不明
    
    '結果を受け取る
    '単独の値
    On Error Resume Next
     Set myResult = doc.getElementById("result")
     If Not myResult Is Nothing Then
      Set myResult = doc.getElementById("result")
     End If
     On Error GoTo 0
     
     '配列はliに入れてみる。
     On Error Resume Next
     Set myResultArray = doc.getElementById("Array")
     If Not myResultArray Is Nothing Then
        ReDim myArray(myResultArray.ChildNodes.Length)
        i = 1
        For Each myListItem In myResultArray.ChildNodes
          Debug.Print myListItem.innerText
          myArray(i) = myListItem.innerText
          i = i + 1
        Next myListItem       
     End If
     On Error GoTo 0
'    Sleep 50
'    DoEvents: DoEvents: DoEvents
'    生成されたソースの取得
'    Debug.Print doc.getElementsByTagName("html")(0).outerHTML
   
End Sub

'エクセルのシートに図形のTextBoxとボタン(これも図形にマクロ登録で可)を一個ずつ置く
'下記はTextBoxに記入したJavaScript Code(只の実行例です)
'大文字小文字の違い一文字でも動かないのでデバッグはきついです
’IE11の開発者ツールF12でデバッグできないものかと試していますが、うまくいっていません。

//VBAからはmyjscodeという関数名で実行する
//ここで別の関数に振り分ける
function myjscode() { test6();}
function test6(){
  var myArray = [1, 2, 3];
  //var num = myArray.length;
  addarray(myArray);
}
//関数の実行結果をdiv.id='Array'のliとして追記する
//VBAからは doc.getElementById("Array")で取得して、ループを回して行毎に処理する
function addarray(arr){
  var num = arr.length;
  document.write (num);
  
  var body = document.getElementsByTagName('body')[0]; 
  var ul = document.createElement('ul');
  ul.id='Array';
  body.appendChild(ul);
  for (var  i=0 ; i< num  ; i++){ 
  var li = document.createElement('li');
  li.innerText = arr[i];
  ul.appendChild(li);
  li=null;
  }
}
//関数の実行結果をdiv.id="result"のinnerTextとして追記する
//VBAからは doc.getElementById("result").innerTextとして取得できる
//引数は文字列で与えること。文字列への変換例:result.toString(10);
function addresult(result){
   var body = document.getElementsByTagName('body')[0];
  var div = document.createElement('div');
  div.id="result";
  div.innerText=result
  body.appendChild(div);
}


'IEを表示させない方法もやってみました。
'UserForm/WebBrowserを使用しているのは、CreatObjectしたIEを使用する場合、消し忘れの怖れがあるためです。

'☆ 標準モジュール
Sub test()
  Load UserForm1
' UserForm側でUnload Meするとうまくいかない様です
  Unload UserForm1
End Sub

'☆ UserForm1モジュール
Private Declare Sub Sleep Lib "kernel32" (ByVal ms As Long)

'UserFormにはWebBrowserコントロールを一個だけ置きます。
’UserFormは表示させないで処理結果のみを示しています。
Private Sub UserForm_initialize()
    Dim objIE As WebBrowser
    Dim doc As IHTMLDocument
    Dim myElement As IHTMLElement
    Dim newElement As IHTMLElement
    Dim myScript As String
     
    Set objIE = Me.WebBrowser1
    Set doc = New HTMLDocument
    
    doc.write "<html><head></head><body></body></html>"
    Set myElement = doc.getElementsByTagName("head")(0)
    Set newElement = doc.createElement("SCRIPT")
    newElement.Type = "text/javascript"
    myScript = ActiveSheet.Shapes("TextBox 1").TextFrame.Characters.Text
    If myScript = "" Then Exit Sub
    newElement.Text = myScript
'    doc.body.appendChild newElementはOKだが、doc.headはエラーになる
    myElement.appendChild newElement
    
' 問題なく動きます
    doc.script.setTimeout "myjscode()", 10
    Sleep 50
    DoEvents: DoEvents: DoEvents
    Debug.Print doc.getElementsByTagName("html")(0).outerHTML
   
   Set myElement = doc.getElementById("result")
   MsgBox myElement.innerText
End Sub