TechFanDan
TechFanDan

Reputation: 3502

Running two instances of Internet Explorer

I'm attempting to run two instances of Internet Explorer to scrape HTML. The goal is to have one global IE for the majority of the functionality. However, I need one instance for a specific purpose (authentication) which gets destroyed once I'm done.

The reason for this second instance of IE is due to the website's authentication process which will throw an alert() Javascript popup that is hard to acknowledge and close. I'm currently terminating the entire instance of IE in this scenario.

Noting I had been discussing the popup in another post here: Internet Explorer readyState reverts from Complete to Interactive

As soon as I terminate the second instance of IE, using its PID, it also seems to impact the global instance of IE. When I return to the global instance of IE, I get: Run-time error '462': The remote server machine does not exist or is unavailable.

To replicate:

  1. execute function runIE1 (can be run multiple times)
  2. execute function runIE2 (can be run multiple times)
  3. execute function runIE1 to get the error

Module code:

Option Explicit

Public Declare Function GetWindowThreadProcessId Lib "user32" _
                                                 (ByVal lHWnd As Long, _
                                                  ByRef lProcessId As Long) As Long
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal Milliseconds As LongPtr)

Public ie_browser As New InternetExplorer

Sub runIE1()
    Debug.Print "--- runIE1 ---"
    Debug.Print "ie_browser PID: "; ie_browser.hwnd
    With ie_browser
        .Navigate "http://127.0.0.1/good.html"
        .Silent = True
        .Visible = False
    End With
    Debug.Print "ie_browser1 Navigated..."
    
    Do Until ie_browser.readyState = 4: DoEvents: Loop
    Do Until ie_browser.Busy = False: DoEvents: Loop
    Debug.Print "ie_browser should have parsed and rendered the page at this time"

    Debug.Print "--- runIE1 ---"
End Sub

Sub runIE2()
    Debug.Print "--- runIE2 ---"
    Dim ie_browser2_hwnd As Long
    Dim ie_browser2 As InternetExplorer
    
    Set ie_browser2 = CreateObject("InternetExplorer.Application")
    Debug.Print "ie_browser2 PID: "; ie_browser2.hwnd
    
    With ie_browser2
        .Navigate "http://127.0.0.1:9000/ftw/bad.html"
        .Silent = True
        .Visible = False
    End With
    
    Debug.Print "ie_browser2 Navigated..."
    
    Debug.Print "ie_browser2 Start wait..."
    Call waitForIE(ie_browser2)
    Debug.Print "ie_browser2 End wait..."
        
    'close if found
    If Not ie_browser2 Is Nothing Then
        Debug.Print "ie_browser2 not null..."
        ie_browser2_hwnd = ie_browser2.hwnd
        ie_browser2.Quit
        Set ie_browser2 = Nothing
        Debug.Print "ie_browser2 quit, set to null"
        Call KillHwndProcess(ie_browser2_hwnd)
        Debug.Print "terminated ie_browser2 PID: " & ie_browser2_hwnd
    End If
    Debug.Print "--- runIE2 ---"
End Sub

Public Sub waitForIE(i As InternetExplorer)
    Dim ie_hwnd As Long
    
    'Ensure browser has completed
    Do While i.readyState = 4: DoEvents: Loop
    
    'Sleep to ensure that we let the readyState to flip back
    Sleep (250)
    
    'popup occurred!
    If i.readyState = 3 Then
        Debug.Print "waitForIE - Popup occurred"
        ie_hwnd = i.hwnd
        Debug.Print "waitForIE - ie PID: " & ie_hwnd
        i.Quit
        Set i = Nothing
        Debug.Print "waitForIE - quit IE, set to nothing..."
        Call KillHwndProcess(ie_hwnd)
        Debug.Print "waitForIE - Terminated IE process: " & ie_hwnd
    Else
        Do Until i.readyState = 4: DoEvents: Loop
        Do Until i.Busy = False: DoEvents: Loop
        
        Debug.Print "Browser should have parsed and rendered the page at this time"
        Debug.Print "IE State: " & i.readyState & " IE busy: " & i.Busy
    End If
    
End Sub


 
'---------------------------------------------------------------------------------------
' Procedure : KillHwndProcess
' Author    : Daniel Pineault, CARDA Consultants Inc.
' Website   : http://www.cardaconsultants.com
' Purpose   : Terminate a process based on its Windows Handle (Hwnd)
' Copyright : The following is release as Attribution-ShareAlike 4.0 International
'             (CC BY-SA 4.0) - https://creativecommons.org/licenses/by-sa/4.0/
' Req'd Refs: Uses Late Binding, so none required
'
' Input Variables:
' ~~~~~~~~~~~~~~~~
' lHWnd     : Windows Handle number (Hwnd)
'
' Usage:
' ~~~~~~
' Call KillHwndProcess(Application.hWnd)
'
' Revision History:
' Rev       Date(yyyy/mm/dd)        Description
' **************************************************************************************
' 1         2018-09-09              Initial Website Release
'---------------------------------------------------------------------------------------
Public Function KillHwndProcess(lHWnd As Long)
' https://learn.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-process
    On Error GoTo Error_Handler
    Dim oWMI                  As Object
    Dim oProcesses            As Object
    Dim oProcess              As Object
    Dim lProcessId            As Long
    Dim sSQL                  As String
    Const sComputer = "."
 
    'Retrieve the ProcessId associated with the specified Hwnd
    Call GetWindowThreadProcessId(lHWnd, lProcessId)
 
    'Iterate through the matching ProcessId processes and terminate
    '   each one.
    Set oWMI = GetObject("winmgmts:\\" & sComputer & "\root\cimv2")
    sSQL = "SELECT * FROM Win32_Process WHERE ProcessId=" & lProcessId
    Set oProcesses = oWMI.ExecQuery(sSQL)
    For Each oProcess In oProcesses
        oProcess.Terminate
    Next
 
Error_Handler_Exit:
    On Error Resume Next
    If Not oProcess Is Nothing Then Set oProcess = Nothing
    If Not oProcesses Is Nothing Then Set oProcesses = Nothing
    If Not oWMI Is Nothing Then Set oWMI = Nothing
    Exit Function
 
Error_Handler:
    MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
           "Error Number: " & Err.Number & vbCrLf & _
           "Error Source: KillHwndProcess" & vbCrLf & _
           "Error Description: " & Err.Description & _
           Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
           , vbOKOnly + vbCritical, "An Error has Occurred!"
    Resume Error_Handler_Exit
End Function

Output in immediate window:

ie_browser PID: 593524
--- runIE1 ---
ie_browser PID:  593524 
ie_browser Navigated...
ie_browser should have parsed and rendered the page at this time
--- runIE1 ---
--- runIE1 ---
ie_browser PID:  593524 
ie_browser Navigated...
ie_browser should have parsed and rendered the page at this time
--- runIE1 ---
--- runIE1 ---
ie_browser PID:  593524 
ie_browser Navigated...
ie_browser should have parsed and rendered the page at this time
--- runIE1 ---
--- runIE2 ---
ie_browser2 PID:  397928 
ie_browser2 Navigated...
ie_browser2 Start wait...
waitForIE - Popup occurred
waitForIE - ie PID: 397928
waitForIE - quit IE, set to nothing...
waitForIE - Terminated IE process: 397928
ie_browser2 End wait...
--- runIE2 ---
--- runIE1 ---

File bad.html (remove alert for good.html)

<html>
<head>
<title>Bad file</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> 
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>

<body>
Bad!
<script type="text/javascript">
    alert("Hello World!");
</script>
</body>
</html>

Upvotes: 0

Views: 143

Answers (1)

Tim Williams
Tim Williams

Reputation: 166745

In a quick test this Windows API approach seemed to work for me:

Option Explicit

Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
           (ByVal lpClassName As String, ByVal lpWindowName As String) As Long

Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, _
            ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long

Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
        (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, _
                                                        lParam As Any) As Long

Private Const BM_CLICK As Integer = &HF5
Private Const WM_ACTIVATE As Integer = &H6
Private Const WA_ACTIVE As Integer = 1

Sub TestAPI()

    Dim IE As InternetExplorer, el, hwnd As Long, btn As Long
    Set IE = New InternetExplorerMedium
    
    'open a test document with an auto-alert (using your example)
    With IE
        .Visible = False
        .navigate "http://localhost/testpages/Bad.html"
    End With
    
    Application.Wait Now + TimeSerial(0, 0, 3)
    
    'find the alert
    hwnd = FindWindow("#32770", "Message from webpage")
    
    If hwnd <> 0 Then
        btn = FindWindowEx(hwnd, 0, "Button", "OK") 'find the OK button
        If btn <> 0 Then ' button found
            ' activate the button on dialog first or it
            '   may not acknowledge a click msg on first try
            SendMessage btn, WM_ACTIVATE, WA_ACTIVE, 0
            ' send button a click message
            SendMessage btn, BM_CLICK, 0, 0
        Else
            MsgBox "button not found!"
        End If
    End If
    
    IE.Visible = True 'make visible to ensure the prompt is gone...
    
End Sub

Upvotes: 3

Related Questions