I’m writing a small application that will launch Internet Explorer and will open a unknown list or URLs, either as new windows or as new tabs within existing windows (depending on the setting for that particular site). The websites being launched can be in any internet zone. I am able to use the SHDocVw methods to open new Windows and tabs.
I’m trying to figure out a way to track the last opened Internet Explorer reference, so that I can use that reference to open tabs.
I’m running into the situation where, due to “loosely-coupled Internet Explorer” (LCIE) and IE protected mode, the IE instance that I launch gets shut down and another automatically launched (IE virtual tab switching). This causes me to lose the reference that I had to the original IE and when I try opening a tab it fails.
I would like to use the ShellWindows FindWindowSW method to get a specific Window (based on the ShellWindows cookie value), but I can’t get it to work.
Could someone point me in the right direction?
private InternetExplorer GetLastExplorer(int cookie)
{
object _m = Type.Missing;
const int SWC_BROWSER = 0x00000001;
const int SWFO_COOKIEPASSED = 4;
int pHWND;
_shellWindows.FindWindowSW(cookie, ref _m, SWC_BROWSER, out pHWND, 5);
foreach (InternetExplorer window in _shellWindows)
{
if (window.HWND == pHWND)
return window;
}
return null;
}
I couldn’t get this to work, and had to take a different approach. I ended up doing the following to get the last opened IE instance:
private InternetExplorer _lastInternetExplorer;
private List<InternetExplorer> _existingInternetExplorers = new List<InternetExplorer>();
private static ShellWindows _shellWindows = new ShellWindows();
_shellWindows.WindowRegistered += OnShellWindowRegistered;
private void OnShellWindowRegistered(int lCookie)
{
foreach (InternetExplorer window in _shellWindows)
{
if (!_existingInternetExplorers.Contains(window))
{
_lastInternetExplorer = window;
_existingInternetExplorers.Add(window);
}
}
}
Related
I have a WPF app that will be acting like a desktop, with certain apps available, such as Microsoft Edge browser. I am able to open multiple windows of it, add a button to a custom taskbar, but the problem is, you could find the process by name, but then the buttons/program doesnt know which correct window to open. And if I search by ID, VS gives me:
System.ArgumentException:'Process with an Id of #### is not running'
Since Microsoft it self, changes the ID of the initial ID that it starts with.
So the methods right now im using for trying to get the right window is:
public void NewTabClick(object sender, RoutedEventArgs e)
{
//NewTabClick is a click event that gets created with every new button
MessageBox.Show("NewTabClicked");
ShowWindow(GetWindowHandle(), 3);
}
private IntPtr GetWindowHandle()
{
try
{
//Here is where i get the exception error
return Process.GetProcessById(ProcessId).MainWindowHandle;
}
catch (NullReferenceException)
{
return IntPtr.Zero;
}
}
ProcessId is set when we start the app itself in another method like this
private void StartApp()
{
var process = Process.Start(ProgramPath);
SetProcessId(process);
}
private void SetProcessId(Process process)
{
ProcessId = process.Id;
}
Is there a way to keep track/change/update which ID it should look for, or another way to keep track of these windows/browsers?
Thanks in advance!
In a WPF C# app, users can launch the "explorer.exe" process from a given menu.
This is achieved as usual, with
Process.Start("explorer.exe");
However, I need to restrict the explorer quantity of simultaneous processes to one instance, instead of as many instances as the user starts by clicking on a button.
So the usual way is to count how many instance of the given process, "explorer.exe" are actually running and if there is more than one, then block the Process.Start().
The issue is that I'm stucked in the counting function. here is what I wrote:
static bool CountProcess(string name) {
return false; // by defualt it returns false.
int counter = 0;
while(true) {
counter = Process.GetProcessesByName(name).length; // get the quantity of processes for a given name.
if(counter > 1) {
return true;
break;
}
}
}
Then I invoke the function as this:
if(countProcess("explorer")) {
// Do nothing.
} else {
Process p = Process.Start("explorer.exe");
}
However after build and execute, the app gets stucked when opening the given process. Indeed, Visual Studio does not give any debug feedback.
How can this function be refactored to be 1) operational, 2) efficient.
Why there is while loop in CountProcess method? It should be simple if.
if(Process.GetProcessByName("explorer").Length == 0)
{
Process.Start("explorer.exe");
}
=== UPDATE ===
Ok, I'm starting to realize what is your problem.
If this wasn't explorer.exe - this code should work:
private static Process proc { get; set; }
private void button1_Click(object sender, EventArgs e)
{
if (proc == null || proc.HasExited)
{
proc = Process.Start("explorer.exe");
}
}
It checks whether Process was ever created (if first time - allow processing, if not - deny starting a new one) If he clicks for the second time, the process is not null but it SHOULD BE as proc.HasExited == false (if you didn't close it)
But if you run this code - probably starting new explorer window will be possible because this newly created process is being closed immediately. And this is because:
The reason that WaitForSingleObject returns immediately is that Explorer is a single-instance program (well, limited-instance)
You can try modifying the registry as proposed here :
Open explorer window and wait for it to close
But if this to be client application to be installed on others computer, I wouldn't advise changing programmatically someone registry.
=== UPDATE 2 ====
This solution below works - but with some restrictions (You must add com reference: "Microsoft Internet Controls") It allows to open one explorer window - and then checks whether window with the same "start folder path" as the base is already opened (watch out for slash and backslash difference in two different places of the code)
using SHDocVw;
public bool ExistOpenedWindow()
{
ShellWindows _shellWindows = new SHDocVw.ShellWindows();
string processType;
foreach (InternetExplorer ie in _shellWindows)
{
//this parses the name of the process
processType = Path.GetFileNameWithoutExtension(ie.FullName).ToLower();
//this could also be used for IE windows with processType of "iexplore"
if (processType.Equals("explorer") && ie.LocationURL.Contains("C:/"))
{
return true;
}
}
return false;
}
private void button1_Click(object sender, EventArgs e)
{
if (proc == null || !ExistOpenedWindow())
{
proc = Process.Start("explorer.exe", #"C:\");
}
}
So if you choose your base path (which will be sent as argument to explorer.exe") to be C:/, after clicking button once again, it will check whether there is ANY explorer window containing such path (opened by you or not)
Compare here: Start explorer.exe without creating a window C#
And here: Is there a way to close a particular instance of explorer with C#?
=== UPDATE 3 ====
After some thoughts - i've managed to come to working solution:
public bool ExistOpenedWindow()
{
var currentlyOpenedWindows = GetAllOpenedExplorerWindow();
return currentlyOpenedWindows.Any(t => t.HWND == ActiveOpenedWindowHwnd);
}
public List<InternetExplorer> GetAllOpenedExplorerWindow()
{
List<InternetExplorer> windows = new List<InternetExplorer>();
ShellWindows _shellWindows = new SHDocVw.ShellWindows();
string processType;
foreach (InternetExplorer ie in _shellWindows)
{
//this parses the name of the process
processType = Path.GetFileNameWithoutExtension(ie.FullName).ToLower();
//this could also be used for IE windows with processType of "iexplore"
if (processType.Equals("explorer"))
{
windows.Add(ie);
}
}
return windows;
}
public static int ActiveOpenedWindowHwnd;
private void button1_Click(object sender, EventArgs e)
{
var currentlyOpenedWindows = GetAllOpenedExplorerWindow();
if (ActiveOpenedWindowHwnd == 0 || !ExistOpenedWindow())
{
Process.Start("explorer.exe");
ShellWindows windows;
while ((windows = new SHDocVw.ShellWindows()).Count <= currentlyOpenedWindows.Count)
{
Thread.Sleep(50);
}
var currentlyOpenedWindowsNew = GetAllOpenedExplorerWindow();
var openedWindow = currentlyOpenedWindowsNew.Except(currentlyOpenedWindows).Single();
ActiveOpenedWindowHwnd = openedWindow.HWND;
}
}
So I'm working with selenium firefox webdrivers in c# winform and I have this code below to get the handle of the popup that shows when you click on the "webtraffic_popup_start_button" and it should get the handle of the popup but the popup handle is same as current one.
string current = driver.CurrentWindowHandle;
driver.FindElement(By.XPath("//*[#id='webtraffic_popup_start_button']")).Click();
Thread.Sleep(Sleep_Seconds);
popup = driver.CurrentWindowHandle;
Thread.Sleep(3000);
driver.SwitchTo().Window(current);
Thread.Sleep(1000);
Any help with this would be much appreciated thank you
This is what pop up looks like.
WebDriver does absolutely no tracking whatsoever to detect which window is actually in the foreground in the OS, and does no automatic switching when new browser windows are opened. That means the proper way to get the handle of a newly-opened popup window is a multi-step process. To do so, you would:
Save the currently-focused window handle into a variable so that you
can switch back to it later.
Get the list of currently opened window handles.
Perform the action that would cause the new window to appear.
Wait for the number of window handles to increase by 1.
Get the new list of window handles.
Find the new handle in the list of handles.
Switch to that new window.
In code using the .NET language bindings, that would look something like this:
string currentHandle = driver.CurrentWindowHandle;
ReadOnlyCollection<string> originalHandles = driver.WindowHandles;
// Cause the popup to appear
driver.FindElement(By.XPath("//*[#id='webtraffic_popup_start_button']")).Click();
// WebDriverWait.Until<T> waits until the delegate returns
// a non-null value for object types. We can leverage this
// behavior to return the popup window handle.
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));
string popupWindowHandle = wait.Until<string>((d) =>
{
string foundHandle = null;
// Subtract out the list of known handles. In the case of a single
// popup, the newHandles list will only have one value.
List<string> newHandles = driver.WindowHandles.Except(originalHandles).ToList();
if (newHandles.Count > 0)
{
foundHandle = newHandles[0];
}
return foundHandle;
});
driver.SwitchTo().Window(popupWindowHandle);
// Do whatever you need to on the popup browser, then...
driver.Close();
driver.SwitchTo().Window(currentHandle);
Alternatively, if you're using the .NET bindings, there's a PopupWindowFinder class in the WebDriver.Support assembly that is specifically designed to do these operations for you. Using that class is much simpler.
// Get the current window handle so you can switch back later.
string currentHandle = driver.CurrentWindowHandle;
// Find the element that triggers the popup when clicked on.
IWebElement element = driver.FindElement(By.XPath("//*[#id='webtraffic_popup_start_button']"));
// The Click method of the PopupWindowFinder class will click
// the desired element, wait for the popup to appear, and return
// the window handle to the popped-up browser window. Note that
// you still need to switch to the window to manipulate the page
// displayed by the popup window.
PopupWindowFinder finder = new PopupWindowFinder(driver);
string popupWindowHandle = finder.Click(element);
driver.SwitchTo().Window(popupWindowHandle);
// Do whatever you need to on the popup browser, then...
driver.Close();
// Switch back to parent window
driver.SwitchTo().Window(currentHandle);
If the lastly opened window is your target then simply do the following after the click
driver.SwitchTo().Window(driver.WindowHandles.ToList().Last());
EDIT
//You may need to go back to parent window to perform additional actions;
// to the new window
driver.SwitchTo().Window(driver.WindowHandles.ToList().Last());
// to the new window
driver.SwitchTo().Window(driver.WindowHandles.ToList().First());
//or
driver.SwitchTo().DefaultContent();
I've got some code you might like. The quickest solution is to use Popup Finder, but I've made my own method as well. I would never rely on the order the Window Handles are in to select the appropriate window. Popup Window Finder:
PopupWindowFinder finder = new PopupWindowFinder(driver);
driver.SwitchTo().Window(newWin);
My Custom method. Basically you pass it the element you want to click, your webdriver, and optionally the time to wait before searching after you click the element.
It takes all of your current handles and makes a list. It uses that list to eliminate the previously existing windows from accidentally getting switched to. Then it clicks the element that launches the new window. There should always be some sort of a delay after the click, as nothing happens instantly. And then it makes a new list and compares that against the old one until it finds a new window or the loop expires. If it fails to find a new window it returns null, so if you have an iffy webelement that doesn't always work, you can do a null check to see if the switch worked.
public static string ClickAndSwitchWindow(IWebElement elementToBeClicked,
IWebDriver driver, int timer = 2000)
{
System.Collections.Generic.List<string> previousHandles = new
System.Collections.Generic.List<string>();
System.Collections.Generic.List<string> currentHandles = new
System.Collections.Generic.List<string>();
previousHandles.AddRange(driver.WindowHandles);
elementToBeClicked.Click();
Thread.Sleep(timer);
for (int i = 0; i < 20; i++)
{
currentHandles.Clear();
currentHandles.AddRange(driver.WindowHandles);
foreach (string s in previousHandles)
{
currentHandles.RemoveAll(p => p == s);
}
if (currentHandles.Count == 1)
{
driver.SwitchTo().Window(currentHandles[0]);
Thread.Sleep(100);
return currentHandles[0];
}
else
{
Thread.Sleep(500);
}
}
return null;
}
This is web single sign on code that runs on a .net 3.5 winform. The code runs fine for ie6 or ie8 as long as ie8 only has one tab open. The problem is that if the user opens a new tab (tab 2,3,etc.) and navigates to a web site (web form internal in the organization) the below code will be executed but the ie COM automation object will return the HTMLDocument for the first tab (Tab 1) even though tab 2 is the active tab. I can't find any IE tab references in the InternetExplorer or HTMLDocument classes anywhere. Actually, there's very little IE tab related documentation anywherer in the IE COM automation docs.
AutoResetEvent ie2_NavigateCompleteAutoReset;
/// <summary>
/// Given the handle of an Internet Explorer instance, this method performs single sign on to
/// several known web login forms.
/// </summary>
/// <param name="iEFramHandle"></param>
private void WebFormSignOn(int iEFramHandle)
{
foreach (SHDocVw.InternetExplorer ie2 in new SHDocVw.ShellWindows())
{
if (ie2.HWND == iEFramHandle)
{
while (true)
{
Thread.Sleep(100);
if (ie2.ReadyState == SHDocVw.tagREADYSTATE.READYSTATE_COMPLETE)
{
try
{
mshtml.HTMLDocument doc = (mshtml.HTMLDocument)ie2.Document;
ie2.NavigateComplete2 += new SHDocVw.DWebBrowserEvents2_NavigateComplete2EventHandler(ie2_NavigateComplete2);
ie2_NavigateCompleteAutoReset = new AutoResetEvent(false);
/*Find the username element and enter the user's username*/
mshtml.HTMLInputElement userID = (mshtml.HTMLInputElement)doc.all.item("username", 0);
userID.value = Globals.Username;
/*Find the password element and enter the user's password*/
mshtml.HTMLInputElement pwd = (mshtml.HTMLInputElement)doc.all.item("password", 0);
pwd.value = Globals.GetAppName();
/*Find the submit element/button and click it*/
mshtml.HTMLInputElement btnsubmit = (mshtml.HTMLInputElement)doc.all.item("submit", 0);
btnsubmit.click();
/*Wait up to 5 seconds for the form submit to complete.
This is to prevent this method from being called multiple times
while waiting for the form submit and subsequent navigation from completing.*/
ie2_NavigateCompleteAutoReset.WaitOne(5000);
return;
}
catch (Exception err)
{
Logger.Log(err.ToString(), Logger.StatusFlag.Error, this.ToString(), "WebFormSignOn");
return;
}
finally
{
/*Remove the event handler*/
ie2.NavigateComplete2 -= ie2_NavigateComplete2;
}
}
}
}
}
}
void ie2_NavigateComplete2(object pDisp, ref object URL)
{
ie2_NavigateCompleteAutoReset.Set();
}
It turns out that each tab in IE 8 has it's own process and handle. In the original code i was always getting the handle from the first IEFrame. I modified the code (below) and now it works. The change is that instead of looking for just the first IEFrame handle, the code also looks for a LocationURL that matches the url that triggerd the method that calls WebFormsSignOut.
private void WebFormSignOn(int iEFramHandle,string addressBarText)
{
var shellWindows = new SHDocVw.ShellWindows();
foreach (SHDocVw.InternetExplorer ie2 in shellWindows)
{
if (ie2.LocationURL==addressBarText)
{ //rest of the code (see orignal post)
Internet Explorer does not have any public tab APIs (beyond allowing you to target a navigation to a new foreground or background tab). Each ActiveX control or BHO is loaded individually into an individual tab instance. Trying to walk down from the ShellWindows collection isn't likely to work in general, instead you should have your plugin reach out to its hosting site (e.g. IObjectWithSite::SetSite will convey this info) which will allow you to determine your hosting tab.
I am writing a BHO for IE using C#. The code I'm concerned with is this:
public class BHO : IObjectWithSite, IOleCommandTarget
{
...
public BHO()
{
MessageBox.Show("Constructor called");
}
public int SetSite(object site)
{
MessageBox.Show("SetSite called!");
if( site != null )
{
_webBrowser = (WebBrowser) site;
_webBrowser.NavigateComplete2 += OnNavigateComplete2;
}
else
{
_webBrowser.NavigateComplete2 -= OnNavigateComplete2;
_webBrowser = null;
}
return 0;
}
private void OnNavigateComplete2(object pDisp, ref object URL)
{
MessageBox.Show("OnNavigateComplete2 called");
}
When IE is run with Protected Mode off, everything works fine. However, if Protected Mode is turned on, NavigateCompleted2() is called, but SetSite() and the constructor are never called (!?!). However, if I create a menu item which calls a method in the BHO class, or open a new tab, everything is correctly called.
Does anyone know why it doesn't work when I open a new IE window?
The full source listing can be found here.
Someone on MSDN answered my question: the constructor and method were still being called, but for some reason the MessageBoxes don't show when I open a new window in Protected Mode until the page is loaded. Variables weren't being set due to a different problem - the constructor was instantiating an object which was silently failing.
I now need help with a different (very much related) problem.