MicrosoftEdgeのIEモードをVBAで操作する

スポンサーリンク

InternetExplorerが2022年6月13日以降使用できなくなる?

先日ネットの記事でInternetExplorerが使えなくなるとの話がありました。
どうやらサポート終了日以降はIEを起動しても強制的にMicrosoftEdgeにリダイレクトされるとのことでした。

以前からIEはセキュリティ上の問題が指摘されていていずれ使えなくなるという話はありましたが、サポート終了日以降も起動くらいは問題ないと思っていたので、この対応にかなり焦りました。

手作業の業務についてはMicrosoftEdgeのIEモードを使用すればほぼほぼ問題ないのですが、VBAを使用したDOM(DocumentObjectModel)を取得する方法が一切使えなくなるのがネックでした。

今回私が慌てて代替の方法を色々と検討した結果、先人が素晴らしいコードを作成してくれているのを見つけました。
また、それを活用することで今までのIE操作で使用していたコードをほぼそのまま使用できることがわかりましたのでお困りの方にお伝えしたくコードと使い方をご紹介します。

VBAでIEを操作する方法の代替手段

一般的なブラウザ操作の自動化手段について

VBAでIEを操作して情報を取得したり、ブラウザ上の操作を自動化する方法についての代替手段はこれまで以下のものが挙げられていました。
ただ後述するようにこれらの方法では色々な制約があり、これらの方法は試してみたものの私の行っている業務の代替手段としては使用できませんでした。

①SeleniumBasicを使う

Pythonや他のプログラムなどでも使用されるSeleniumのVBA版を使用してブラウザの操作を自動化する方法です。

IEからDOMを取得する方法と少し挙動は異なりますが、どちらかというとIEからDOMを取得する方法よりSeleniumを使う方法の方が主流のため、ネット上で情報も多く手に入ります。

ウェブサイトのHTMLから特定の要素を簡単に特定できるCSSセレクタを使えて、Seleniumに慣れているエンジニアの方が多いからか、色々なサイトでSeleniumをおすすめされています。

しかし私のようにエンジニアではなく普段会社で事務仕事をしている場合Seleniumを使用する場合以下のような壁に当たります。

1.VBAでDOMを取得してIEを操作する場合と異なり、ブラウザの起動から終了までをすべてプログラムで操作する必要がある。

開いていたページをそのまま操作することができないので、二段階認証が必要なログインの処理などが間に入ると操作ができないケースがあります。

2.Seleniumを使っていることがサーバー側に伝わるためにページの情報が取得できないことがある

例えばGoogleのサインインした後の個人のページなどはSeleniumでアクセスしようとすると拒否され、操作ができないことがあります。

3.私も詳細がわからないのですが、SeleniumとWebドライバでブラウザを開こうとすると拒否されるケースがありました。

おそらく会社の社内ネットワークセキュリティのどこかに引っかかっていると思うのですが、原因は特定できておりません。
とりあえず環境次第でSeleniumは使用できない場合があるようです。

4.そもそもSeleniumBasicをインストールすることが禁止されている。

会社からの貸与PCは自由にアプリケーションをインストールすることができない場合も多く、私もここが大きな問題となります。

また、そのほかVBAで使用するためのSeleniumBasicは少し前から更新されておらず、ドライバを手動で更新してから使用しないといけないなど今後の安定的な使用について若干の不安がありました。

②PowerAutomateDeskTopなどのRPAを使用する

Windows標準のRPAであるPowerAutomateDeskTopやその他のRPAツールで操作を代替する紹介をされている場合も多いです。

ただ、専門的な操作をしようとすると有料のサービスを使用する必要があることや、IEからDOMを取得するVBAを書く場合と異なる操作が多く、習熟するのに少し時間がかかる印象でした。

スポンサーリンク

満を持してのEdgeを操作するVBAコードの登場

Seleniumは使えず、(作り慣れていないので)RPAを作るには時間がかかるうえに大掛かりな変更が必要になることから別の手段を検討していたところ、以下の2つの記事からEdgeでDOMを取得して操作する方法が紹介されていました。

https://www.ka-net.org/blog/?p=6033

https://social.msdn.microsoft.com/Forums/ja-JP/c0765a67-b8ba-40dc-ac52-aac7be9f1d6a/ie123981246912509125401248812364202224180626376152608512395320662?forum=vbajp

上の初心者備忘録は専門的な知識を使ったコードをたくさん紹介してくださっているサイトで、私も普段よく参考にさせていただいています。

この記事は以前のChromiumベースになる前のEdge操作について書かれていますが、新しいEdgeについてもIEモードではClass「InternetExplorer_Server」が存在していて、基本的に同じ処理ができるそうです。

MicrosoftEdgeのIEモードの操作サンプルコード

上記の2つの記事の情報を合わせて、かつ長くなるDOM取得部分を一個のコードで使いまわせるように以下の通りコードを書いてみました。

以下の「検索実行」のプロシージャを実行するとEdgeを開き、IEモードに切り替えた後、Googleで「VBA・GAS・Pythonで業務を楽しく効率化」を検索するという操作を自動化しています。

宣言部分

Option Explicit

#If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal ms As LongPtr)
#Else
Private Declare Sub Sleep Lib "kernel32" (ByVal ms As Long)
#End If

Private Declare Function GetTopWindow Lib "user32" (ByVal hWnd As Long) As Long
Private Declare Function GetParent Lib "user32" (ByVal hWnd As Long) As Long
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long, lpdwProcessId As Long) As Long
Private Declare Function EnumChildWindows Lib "user32" (ByVal hWndParent As Long, ByVal lpEnumFunc As Long, lParam As Long) As Long
Private Const GW_HWNDNEXT = &H2
Private Declare Function GetNextWindow Lib "user32" Alias "GetWindow" (ByVal hWnd As Long, ByVal wFlag As Long) As Long
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare Function IIDFromString Lib "ole32" (lpsz As Any, lpiid As Any) As Long
Private Declare Function RegisterWindowMessage Lib "user32" Alias "RegisterWindowMessageA" (ByVal lpString As String) As Long
Private Const SMTO_ABORTIFHUNG = &H2

Private Declare Function SendMessageTimeout Lib "user32" Alias "SendMessageTimeoutA" (ByVal hWnd As Long, ByVal msg As Long, _
                            ByVal wParam As Long, ByVal lParam As Long, ByVal fuFlags As Long, ByVal uTimeout As Long, lpdwResult As Long) As Long
Private Declare Function ObjectFromLresult Lib "oleacc" (ByVal lResult As Long, riid As Any, ByVal wParam As Long, ppvObject As Object) As Long
        
'オブジェクトの位置取得?
Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0 To 7) As Byte
End Type

Private Declare PtrSafe Function IUnknown_QueryService Lib "shlwapi.dll" _
    (ByVal punk As IUnknown, guidService As GUID, riid As GUID, ppvOut As IAccessible) _
        As Long

Private hIES As Long
Private IeEdge As Object
Private hWnd As Long

'EdgeをIEモードに切り替える場合に使用する
'対象ウインドウを最前面にする
Private Declare Sub SetForegroundWindow Lib "user32" (ByVal hWnd As Long)

'キーボード操作
Private Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal bScan As Byte, _
                                                ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
Private Const KEYEVENTF_EXTENDEDKEY = &H1
Private Const KEYEVENTF_KEYUP = &H2
Private Const fKEYDOWN = KEYEVENTF_EXTENDEDKEY
Private Const fKEYUP = KEYEVENTF_EXTENDEDKEY Or KEYEVENTF_KEYUP

Private Const VK_UP = &H26
Private Const VK_Alt = &H12
Private Const VK_F = &H46
Private Const VK_RETURN = &HD

Private Sub DOM取得()

    'Chromium版EdgeのIEモードをDOM操作(32ビット版Excel)
    
    Dim con As Object, items As Object
    Dim pId As Long
    Const ProcessName = "msedge.exe"

    Dim msg As Long
    Dim res As Long
    Dim IID_IHTMLDocument2 As GUID

    Dim Dom

    Set con = CreateObject("WbemScripting.SWbemLocator").ConnectServer
    hWnd = GetTopWindow(0)
        Do
            If GetParent(hWnd) = 0 Then
                'ウィンドウハンドルからプロセスIDを取得し、プロセス名を使用してEdgeのウィンドウかどうかを判別する
                GetWindowThreadProcessId hWnd, pId
                Set items = con.ExecQuery("Select * From Win32_Process Where (ProcessId = '" & pId & "') And (Name = '" & ProcessName & "')")
                If items.Count > 0 Then
                    'Edgeの子ウィンドウ列挙
                    EnumChildWindows hWnd, AddressOf EnumChildProcIES, 0 'Internet Explorer ServerのhWndをhIESに格納
                    If hIES <> 0 Then Exit Do
                End If
            End If
            hWnd = GetNextWindow(hWnd, GW_HWNDNEXT)
        Loop While hWnd <> 0

    If hIES = 0 Then Exit Sub

    'IHTMLDocument2取得
    msg = RegisterWindowMessage("WM_HTML_GETOBJECT")
    SendMessageTimeout hIES, msg, 0, 0, SMTO_ABORTIFHUNG, 1000, res
    If res Then
        With IID_IHTMLDocument2
            .Data1 = &H332C4425
            .Data2 = &H26CB
            .Data3 = &H11D0
            .Data4(0) = &HB4
            .Data4(1) = &H83
            .Data4(2) = &H0
            .Data4(3) = &HC0
            .Data4(4) = &H4F
            .Data4(5) = &HD9
            .Data4(6) = &H1
            .Data4(7) = &H19
        End With
        
        'IeEdgeを使いたいために適当にDomという変数を宣言して左辺に
        '正しくは「If ObjectFromLresult(res, IID_IHTMLDocument2, 0, IeEdge) = 0 Then」などの使用方法が望ましい?
        
        Dom = ObjectFromLresult(res, IID_IHTMLDocument2, 0, IeEdge)
    
    End If

End Sub

Private Function EnumChildProcIES(ByVal hWnd As Long, ByVal lParam As Long) As Long
  
    Dim buf As String * 255
    Dim ClassName As String

    GetClassName hWnd, buf, Len(buf)
    ClassName = Left(buf, InStr(buf, vbNullChar) - 1)
    If ClassName = "Internet Explorer_Server" Then
        hIES = hWnd
        EnumChildProcIES = False
        Exit Function
    End If
    EnumChildProcIES = True

End Function

実行部分

Sub 検索実行()

    Edge起動
    
    'GoogleのページをIEモードで開いた後に実行
    DOM取得 'IEモードのEdgeのDOMをIeEdgeに格納
    
    '最前面に持ってくるとIEモードのポップアップが消える
    Call SetForegroundWindow(hWnd)
    Sleep 3000
    
    IeEdge.getElementsByName("q")(0).Value = "VBA・GAS・Pythonで業務を楽しく効率化" '検索バーに文字を入力
    Sleep 1000
    IeEdge.getElementsByName("btnG")(0).Click '検索クリック

    While LCase(IeEdge.readyState) <> "complete"
        Sleep 1000
    Wend
    
End Sub

Private Sub Edge起動()

    'EdgeでGoogleのページを開く
    CreateObject("Shell.Application").ShellExecute "microsoft-edge:https://www.google.co.jp/"
    Sleep 3000

    'Alt+F
    keybd_event VK_Alt, 0, fKEYDOWN, 0   'Altを押す
    keybd_event VK_F, 0, fKEYDOWN, 0   'fを押す
    keybd_event VK_F, 0, fKEYUP, 0   'fを離す
    keybd_event VK_Alt, 0, fKEYUP, 0   'Altを離す

    Sleep 1000

    '上5回
    Dim i
    For i = 1 To 5
        keybd_event VK_UP, 0, fKEYDOWN, 0   'UPを押す
        keybd_event VK_UP, 0, fKEYUP, 0   'UPを離す
        Sleep 100
    Next i
        
    'Enter
    keybd_event VK_RETURN, 0, fKEYDOWN, 0   'Enterを押す
    keybd_event VK_RETURN, 0, fKEYUP, 0   'Enterを離す

    Sleep 1000

    'Enter
    keybd_event VK_RETURN, 0, fKEYDOWN, 0   'Enterを押す
    keybd_event VK_RETURN, 0, fKEYUP, 0   'Enterを離す

    Sleep 3000
    
End Sub

あくまでIEモードの操作に限られますのでいずれ使えなくなるとは思いますが、これまでVBAでIEを操作していたコードをほぼそのまま転用できるのは大きなメリットです。

IEモードの切り替え部分が若干不安定なこともありますが、最初から特定のページをIEモードで開くように設定しておけば切り替えるコードを使う必要がなく、より安定した挙動になると思います。

このコードはWindowsAPIをたくさん使用していて私も意味が理解できていない部分が多くありますので、勉強してコードの意味が理解できるようになればまた紹介記事を記載します。

コメント