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
上の初心者備忘録は専門的な知識を使ったコードをたくさん紹介してくださっているサイトで、私も普段よく参考にさせていただいています。
この記事は以前の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をたくさん使用していて私も意味が理解できていない部分が多くありますので、勉強してコードの意味が理解できるようになればまた紹介記事を記載します。
コメント
はじめまして。EdgeのIEモードをVBA操作したいと考えており、こちらの記事を参考にさせて頂いております。わかればご教授願いたいのですが、64bit版のエクセルで動かすコードはわかりますでしょうか?こちらの記事のコードは32bit版用だと思われるのですが、64bit版用として現在試しているのが、次の2点です。
①”Long”を”LongPtr”に書き換えた。
②”Private Declare Function”の箇所で、”Declare”と”Function”の間に”PtrSafe”を付け加えた。
Edgeを開き、IEモードで開くところまでは動いたのですが、どうしてもgoogle検索の検索ボックスにキーワード挿入する箇所(IeEdge.getElementsByName(“q”)(0).Value = “VBA・GAS・Pythonで業務を楽しく効率化”)で「オブジェクト型の変数は設定されていません (エラー 91)」というエラーとなってしまいます。考えられる原因がもしわかるようでしたらご教授して頂きたいです。
コメントありがとうございます。
申し訳ありません。
正直64bit版のExcelは使ったことがなく、正直この部分が原因だと
どのように修正すればよいかわからないです。
ただ、原因は別のところなのかな?と思いまして…。
ブラウザはIEモードにきちんと切り替わっておりますでしょうか?
(アドレスバーの左にIEのアイコンが表示されているかどうかで確認ができると思います。)
今私のPCで動作確認してみたら記事作成時からブラウザのバージョンアップで
IEモードへの変更がうまくいかないようです。
そのため、検索文字を入力する段階でDOMを取得できておらず同じエラー91が出ました。
解決方法としては以下の手順をお試しいただけますでしょうか。
①プロシージャの「Edge起動」を以下2行にしてIEモードへ変更するステップの削除
CreateObject(“Shell.Application”).ShellExecute “microsoft-edge:https://www.google.co.jp/”
Sleep 3000
この2行のあとをすべてコメントアウトもしくは削除
②Edgeの「設定」→「既定のブラウザ」→「InternetExplorerの互換性」の欄で
InternetExplorerモード(IEモード)でのサイトの再読み込みを許可:許可
InternetExplorerモードページにhttps://www.google.co.jp/を追加
この状況で再度実行していただき、エラーがどのように生じるか
ご確認いただけると嬉しいです。
お返事ありがとうございます。
IEモードの切り替え自体は、記事のコード(キーボードイベント)により問題無く動いており、視覚でも動作確認できております。念のため、ご指摘の通りに修正して実行してみましたが、同じ箇所でエラーになってしまいました。
ご確認ありがとうございます。
それではおそらく64bit版Excelの手順でWindowsApi対応しないといけなそうですね。
関数の変更については以下のサイトが参考になりそうでした。
https://vbabeginner.net/howto-use-win32-api/
以下のテキストファイルに変換後の記載方法も記載されているので、user32のAPIは修正できると思われます。
https://vbabeginner.net/data/Win32API_PtrSafe.TXT
問題は以下3つのAPIだと思いますが、こちらに関しては情報がなく私もわかりませんでした。
Private Declare Function IIDFromString Lib “ole32” (lpsz As Any, lpiid As Any) 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 Declare PtrSafe Function IUnknown_QueryService Lib “shlwapi.dll” _
(ByVal punk As IUnknown, guidService As GUID, riid As GUID, ppvOut As IAccessible) _
As Long
私も他のサイトの記載を引用して作ったコードなので、詳細が把握てきておらずお役に立てなくて申し訳ありません。
私も探ってみますがかなり難易度が高い気がしており、解決できる自信がないです。
追記情報:以下サイトに64bit Excel対応の記事がありました。
https://qiita.com/ymd65536-ms/items/320f889e9fc35fe375f1
こちらのコードで動くかどうか確認をお願いできますでしょうか。
私の環境ではエラー91が出て動かないのですが、もしかしたら?と期待しております。
Option Explicit
Public Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal ms As LongPtr)
Private Declare PtrSafe Function GetTopWindow Lib “user32” (ByVal hwnd As LongPtr) As LongPtr
Private Declare PtrSafe Function GetParent Lib “user32” (ByVal hwnd As LongPtr) As LongPtr
Private Declare PtrSafe Function GetWindowThreadProcessId Lib “user32” (ByVal hwnd As LongPtr, lpdwProcessId As LongPtr) As LongPtr
Private Declare PtrSafe Function RegisterWindowMessage Lib “user32” Alias “RegisterWindowMessageA” (ByVal lpString As String) As LongPtr
Private Declare PtrSafe Function SendMessageTimeout Lib “user32” Alias “SendMessageTimeoutA” (ByVal hwnd As LongPtr, ByVal msg As LongPtr, ByVal wParam As LongPtr, ByVal lParam As LongPtr, ByVal fuFlags As LongPtr, ByVal uTimeout As LongPtr, lpdwResult As LongPtr) As LongPtr
Private Declare PtrSafe Function IIDFromString Lib “ole32” (lpsz As Any, lpiid As Any) As Long
Private Declare PtrSafe Function ObjectFromLresult Lib “oleacc” (ByVal lResult As LongPtr, riid As Any, ByVal wParam As LongPtr, ppvObject As Object) As LongPtr
Private Declare PtrSafe Function EnumChildWindows Lib “user32” (ByVal hWndParent As LongPtr, ByVal lpEnumFunc As LongPtr, ByVal lParam As LongPtr) As LongPtr
Private Declare PtrSafe Function GetClassName Lib “user32” Alias “GetClassNameA” (ByVal hwnd As LongPtr, ByVal lpClassName As String, ByVal nMaxCount As LongPtr) As LongPtr
Private Declare PtrSafe Function GetNextWindow Lib “user32” Alias “GetWindow” (ByVal hwnd As LongPtr, ByVal wFlag As LongPtr) As LongPtr
Private Const GW_HWNDNEXT = &H2
Private hIES As LongPtr
Public Function GetWindow(Title As String) As Object
Dim con As Object
Dim items As Object
Dim HtmlDoc As Object
Dim hwnd As LongPtr: hwnd = 0
Dim pid As LongPtr: pid = 0
Dim buf As String * 255
Dim ClassName As String
Const ProcessName = “msedge.exe”
Set con = CreateObject(“WbemScripting.SWbemLocator”).ConnectServer
hwnd = GetTopWindow(0)
Do
GetClassName hwnd, buf, Len(buf)
ClassName = Left(buf, InStr(buf, vbNullChar) – 1)
If InStr(ClassName, “Chrome_WidgetWin_”) > 0 Then
‘ウィンドウハンドルからプロセスIDを取得し、Edgeのウィンドウかどうかを判別する
GetWindowThreadProcessId hwnd, pid
Set items = con.ExecQuery(“Select ProcessId From Win32_Process Where (ProcessId = ‘” & pid & “‘) And (Name = ‘” & ProcessName & “‘)”)
If items.Count > 0 Then
‘Edgeの子ウィンドウ列挙
EnumChildWindows hwnd, AddressOf EnumChildProcIES, 0
If hIES <> 0 Then
Set HtmlDoc = GetHTMLDocumentFromIES(hIES)
If HtmlDoc Is Nothing Then
Else
If InStr(HtmlDoc.Title, Title) > 0 Then
Set GetWindow = HtmlDoc
Exit Do
End If
End If
End If
End If
End If
hwnd = GetNextWindow(hwnd, GW_HWNDNEXT)
Loop While hwnd <> 0
End Function
Private Function EnumChildProcIES(ByVal hwnd As LongPtr, ByVal lParam As LongPtr) As LongPtr
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
Private Function GetHTMLDocumentFromIES(ByVal hwnd As LongPtr) As Object
Dim msg As LongPtr, res As LongPtr
Dim iid(0 To 3) As LongPtr
Dim ret As Object, obj As Object
Const SMTO_ABORTIFHUNG = &H2
Const IID_IHTMLDocument2 = “{332c4425-26cb-11d0-b483-00c04fd90119}”
Set ret = Nothing ‘初期化
msg = RegisterWindowMessage(“WM_HTML_GETOBJECT”)
SendMessageTimeout hwnd, msg, 0, 0, SMTO_ABORTIFHUNG, 1000, res
If res Then
IIDFromString StrPtr(IID_IHTMLDocument2), iid(0)
If ObjectFromLresult(res, iid(0), 0, obj) = 0 Then Set ret = obj
End If
Set GetHTMLDocumentFromIES = ret
End Function
Sub edge操作()
Dim IeObj As Object
IeObj.getElementsByName(“q”)(0).Value = “VBA・GAS・Pythonで業務を楽しく効率化” ‘検索バーに文字を入力
End Sub
お返事と参考URLの紹介ありがとうございます。
edge操作のプロシージャの部分は、edgeを立ち上げるコードは不要なのでしょうか?このコードのままだと、動かなそうでした。難しそうでしたら、あとは調べてみますので、大丈夫です。
ご連絡ありがとうございます。
お力になれず申し訳ありません。
WindowsAPIについて私も勉強してみます。