0

I use the following code to check, if there already exists an Internet Explorer 11 Tab with a given Url Location.

I started with code from here: http://francois-piette.blogspot.de/2013/01/internet-explorer-automation-part-1.html

function GetIERunningInstanceByUrl(FLogWriter: ILogWriter; const Url : String): IWebBrowser2;

  function GetClassName(aHWND : HWND) : String;
  var
      L : Integer;
  begin
      SetLength(Result, MAX_PATH * SizeOf(Char));
      L := WinApi.Windows.GetClassName(aHWND, PChar(Result), Length(Result));
      SetLength(Result, L);
  end;

var
  aShWindows : IShellWindows;
  aIdx         : Integer;
  aDisp: IDispatch;
  aClassName: string;
begin
  FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / Url: ''' + Url + '''.');

  aShWindows := CoShellWindows.Create;
  if not Assigned(aShWindows) then begin
    FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / After CoShellWindows.Create, not Assigned(aShWindows) = TRUE.');
  end;
  FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / aShWindows.Count: ' + IntToStr(aShWindows.Count) + '.');

  for aIdx := 0 to aShWindows.Count - 1 do begin
    aDisp := aShWindows.Item(aIdx);
    if not Assigned(aDisp) then begin
      FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / After aDisp := aShWindows.Item(aIdx=' + IntToStr(aIdx) + '), not Assigned(aDisp) = TRUE.');
    end
    else begin
      if not Supports(aDisp, IID_IWebBrowser2) then begin
        FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / Supports(aDisp, IID_IWebBrowser2) = FALSE.');
      end
      else begin
        Result := aDisp as IWebBrowser2;
        if not Assigned(Result) then begin
          FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / After Result := aDisp as IWebBrowser2, not Assigned(Result) = TRUE.');
        end
        else begin
          aClassName := GetClassName(Result.HWND);
          FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / GetClassName(aShWindows.Item(aIdx=' + IntToStr(aIdx) + ').Result): ''' + aClassName + '''.');
        end;
      end;
    end;

    if Supports(aDisp, IID_IWebBrowser2) then begin
      if Assigned(Result) then begin
        if SameText(GetClassName(Result.HWND), 'IEFrame') then begin
          //if SameText(Url, Result.LocationURL) then begin
          if ContainsText(Result.LocationURL, Url) then begin
            FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / Found, Result.HWND: ' + IntToStr(Result.HWND) + ', Result.LocationURL: ''' + Result.LocationURL + '''.');

            Exit;
          end
          else begin
            FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / Not found, ContainsText(Result.LocationURL, Url) = FALSE, Result.LocationURL: ''' + Result.LocationURL + ''' .');
          end;
        end
        else begin
          aClassName := GetClassName(Result.HWND);
          FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / Not found, SameText(GetClassName(Result.HWND), ''IEFrame'') = FALSE, aClassName: ''' + aClassName + ''' .');
        end;
      end;
    end;
  end;
  // Not found
  Result := nil;

  FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / Not found, After Result = nil, Url: ''' + Url + '''.');
end;

The application is installed on machines, that all have Windows 7 Professional Service Pack 1, 64Bit and Internet Explorer 11 (Version 11.0.9600.18762).

The code works fine on most machines, but there are some machines, where I get an 'Unknown error' in this method, after it was running correctly for several times.

When having the error once, the only way to get the application running again, is to logoff from windows and login again.

Unfortunatelly, I may not debug on those (production) machines, so I got to use poor man's debugging, logging every line... (that is also the reason, why my above code became a little bit ugly to read on some lines, sorry.)

Doing that, I found, that it must be something related to the IShellWindows interface.

10.08.2017 10:33:05 ThreadID: 0x00001A08 - GetIERunningInstanceByUrl / Url: 'https://example.com/'.
10.08.2017 10:33:05 ThreadID: 0x00001A08 - GetIERunningInstanceByUrl / aShWindows.Count: 3.
10.08.2017 10:33:05 ThreadID: 0x00001A08 - GetIERunningInstanceByUrl / GetClassName(aShWindows.Item(aIdx=0).Result): 'CabinetWClass'.
10.08.2017 10:33:05 ThreadID: 0x00001A08 - GetIERunningInstanceByUrl / Not found, SameText(GetClassName(Result.HWND), 'IEFrame') = FALSE, aClassName: 'CabinetWClass' .
10.08.2017 10:33:05 ThreadID: 0x00001A08 - Meldung - Exception: Unbekannter Fehler Retry: 1

(translation: 'Unbekannter Fehler' means 'Unknown error')

In the above log sample, it seems that only the first item from the List of 3 items can be iterated using IShellWindows. Then an exception is raised.

Any help would be appreciated...

Markus
  • 357
  • 2
  • 13
  • This is the place where [FindWindowSW works](https://stackoverflow.com/a/45479861/8041231). – Victoria Aug 17 '17 at 07:24
  • @Victoria: How can I get FindWindowSW to work? it doesn't return a window handle nor a dispatch for me... can you give me a hint (some example code?), how to use it to solve the above problem? – Markus Aug 18 '17 at 13:27
  • I've posted one. Maybe you haven't used `SWC_BROWSER` flag. – Victoria Aug 18 '17 at 13:34

2 Answers2

0

I had a similar problem - or still have with a program that uses the IShellWindows interface. My experience is that it does not depend on the machine, but can happen on any machine, but I did not found out what to do to prevent it.

What helps for me is to stop all explorer processes (not the Internet Explorer process!). I do this from within my program, but you can also do this via the Task Manager for testing. If the Task Bar runs in a seperate Explorer process, you will have to stop that too.

After you have restarted the Explorer, the interface works again. This is slightly better than to have to log off, since you do not need to restart all your applications and you can do this from within your code, but this is of course still no good solution, since the Task Bar will be rebuild during this process.

The code I use to Close all Explorer processes and restart is the following:

function isexplorerwindow(exwnd: hwnd): boolean;
var
  p: array[0..max_path] of Char;
begin
  GetClassName(exwnd, p, SizeOf(p));
  result := ((strcomp(p, 'CabinetWClass') = 0) or (strcomp(p, 'ExploreWClass') = 0));
end;


procedure restartshell;
var
  wnd: hwnd;
  pid: dword;
  processhandle: thandle;
  SL: tstringlist;
  z: integer;
  StartUpInfo: TStartUpInfo;
  ProcessInfo: TProcessInformation;
begin
  if messagebox(0, pchar(_('Restarting the shell will close all explorer windows and the task bar.') + #13#10 +
    _('Do you really want to continue?')), __('Warning'), mb_yesno or mb_defbutton2 or mb_iconquestion) = idno then
    Exit;
  SL := tstringlist.Create;
  wnd := getwindow(getdesktopwindow, gw_child);
  while (wnd <> 0) do
  begin
    if isexplorerwindow(wnd) then
      SL.Add(inttostr(wnd));
    wnd := getwindow(wnd, gw_hwndnext);
  end;
  for z := 0 to SL.count - 1 do
    postMessage(strtoint(SL[z]), $10, 0, 0);
  SL.Free;
  application.ProcessMessages;
  sleep(1000);
  application.ProcessMessages;
  wnd := findwindow('Progman', nil);
  if wnd > 0 then
  begin
    GetWindowThreadProcessId(wnd, pid);
    if (pid > 0) then
    begin
      processhandle := OpenProcess(1, false, pid);
      if (processhandle > 0) then
      begin
        TerminateProcess(processhandle, 0);
        CloseHandle(processhandle);
      end;
    end;
  end;
  application.ProcessMessages;
  sleep(1000);
  application.ProcessMessages;
  FillChar(StartUpInfo, SizeOf(StartUpInfo), #0);
  StartUpInfo.cb := SizeOf(StartUpInfo);
  StartUpInfo.dwFlags := STARTF_USESHOWWINDOW;
  if not CreateProcess(nil, 'explorer.exe', nil, nil, false, CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil, nil,
    StartUpInfo, ProcessInfo) then
    messagebeep(mb_iconstop);
Andre Ruebel
  • 518
  • 3
  • 12
  • What exactly does "have a problem" mean? – Victoria Aug 18 '17 at 07:51
  • It means that like for the original poster, creating the IShellWindows interface results in an error (i.e. throws an exception). – Andre Ruebel Aug 18 '17 at 08:21
  • That's not what happened to the OP. The reference has been created. Internally called `CreateComObject` function would raise a formatted `EOleSysError` exception in case of failure. The last log record is different from that. So as would do any of the `safecall` methods. – Victoria Aug 18 '17 at 08:40
  • Exacty: CreateComObject (called in CoShellWindows.Create) worked... I even could call the count method... it returned a count of 3 items and the Item(idx) method could also be called... it returned item #1, having classname: 'CabinetWClass'. Error before Item #2. – Markus Aug 18 '17 at 13:32
0

You can try this. It should return the topmost IE window that is navigated to the given URL:

function TryGetWebBrowser(const URL: WideString; out Browser: IWebBrowser2): Boolean;
var
  Handle: HWND;
  Unused: OleVariant;
  Location: OleVariant;
  WndIface: IDispatch;
  ShellWindows: IShellWindows;
begin
  Result := False;
  if Succeeded(CoCreateInstance(CLASS_ShellWindows, nil, CLSCTX_LOCAL_SERVER, IID_IShellWindows, ShellWindows)) then
  begin
    Unused := Unassigned;
    Location := URL;
    WndIface := ShellWindows.FindWindowSW(Location, Unused, SWC_BROWSER, Integer(Handle), SWFO_NEEDDISPATCH);
    Result := Assigned(WndIface) and Succeeded(WndIface.QueryInterface(IWebBrowser2, Browser));
  end;
end;
Victoria
  • 7,822
  • 2
  • 21
  • 44
  • ok... this returns something... Is there also a solution, that finds a browser with an URL that contains 'www.example.com', e.g. one that is navigated to 'https://www.example.com/example1', as my above code does? – Markus Aug 18 '17 at 13:44
  • More or less what you have. There's a dangerous part (you're not exiting function when `IShellWindows` reference is not returned). But it's not the reason for what you describe. And that class name thing seems to be pointless (it's undocumented btw.). You can simply partially compare `LocationURL` because one is not allowed to use URL in a Windows Explorer (it opens IE). – Victoria Aug 18 '17 at 14:00