Working with Internet Explorer Object - vb.net

My goal is to create a VB application that reads and writes information to various webpages loaded in Internet explorer.
I have created a program that works exactly as I intend in VBA. I am now trying to re-implement the same programming in VB.
I have a function that looks for and returns an Internet Explorer Object where the input matches the LocationName.
Assuming the target page is loaded, I can work with it. Methods such as getElementByID() work perfectly. If the browser window is closed and reopened, and the code is run again, the results are very inconsistent. The getIE function seems to work, but when trying to use methods like document.getElementByID() a NullReferenceException is thrown.
Does anyone know if there is anything I am missing that I need to include to get the document property to update?
EDIT: I have looked over the NullReferenceException article. It hasn't helped me unfortunately. In case I was unclear in my wording, I would like to reiterate that the same bit of code yields a different result when executed the 2nd time under the same conditions (same webpage open in Internet Explorer).
On the second execution IE.locationName is retrievable but IE.Document.title is not. The problem is definitely with the Document property as far as I can tell. I am truly stumped.
Many thanks
Public Function getIE(targetTitle As String) As SHDocVw.InternetExplorer
' Create the shell application and Collection of open windows
Dim shellObj As Object = CreateObject("Shell.Application")
Dim shellWindows As SHDocVw.ShellWindows = shellObj.Windows()
getIE = Nothing
' Scan through the Collection
For I = (shellObj.Windows.Count - 1) To 0 Step -1
' If found, assign this to the function output and exit early
If InStr(shellWindows(I).LocationName, targetTitle) Then
getIE = shellWindows(I)
Debug.Print("Found: " & shellWindows(I).LocationName)
Exit For
End If
Next I
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim IE As SHDocVw.InternetExplorer
IE = getIE("my site title")
If IE Is Nothing Then
Debug.Print("Site not open")
Exit Sub
End If
' The code always gets this far, and despite the page being found, starts throwing exceptions from here on if the window has been closed and reopened whilst my application has stayed running.
' Sample form data insertion code
IE.Document.getElementById("textbox1").value = "my value"
' Click the submit button
IE.Document.getElementById("submit").click()
' Wait for page to load
While IE.Busy
End While
IE.Document.getElementById("textbox2").value = "my 2nd value"
' done
IE = Nothing
End sub

Related

Is there a way to open an IE window with a user-defined name. When same URL opened, already opened IE window comes to foreground

Here is my requirement which I intend to implement. There is one window application showing some icons which take me to different web sites. When I click on one of the icons, it should open an IE window and append a customized name to it.
So, before opening any website after I click on website icon, I want to check if there is already an IE window open with that customized name, if yes, bring that already opened window to the foreground. If not, open a new IE window.
I have checked various questions posted which are related to what I am looking to achieve, but am somehow not able to get it right. Below is my attempt.
For Each e In shellWins
If InStr(1, e.GetProperty("IEWindowName"), namedWindow, CompareMethod.Text) <> 0 Then
hWnd = e.HWND
myIE = e
End If
Next
If hWnd == -1
Dim p As New Process
Dim psi As New ProcessStartInfo(IEPath, webSiteURL)
p.StartInfo = psi 'Trying to open a new IE window
p.Start()
For Each ie In shellWins
If ie.hwdn = p.MainWindowHandle Then
ie.PutProperty("IEWindowName", namedWindow)
End If
Next
End if
Else
myIE.BringToForeground()
This sometime works and sometimes does not. Is there any better way to do it?
It doesn't work in which situation? Does there any error throw when it doesn't work? If there is, please tell us the detailed error information and in which line it occurs.
Besides, you could try to compare the url to check if the website is already open in IE like this:
Sub Main()
Dim shellWins As SHDocVw.ShellWindows
Dim explorer As SHDocVw.InternetExplorer
shellWins = New SHDocVw.ShellWindows
Dim SQuery As String = "https://www.example.com/"
For Each explorer In shellWins
If explorer.Application.Name = "Internet Explorer" And explorer.LocationURL.Contains(SQuery) Then
explorer.BringToForeground()
End If
Next
shellWins = Nothing
explorer = Nothing
End Sub

VBA-Excel: Filling forms in an IE Window

I'm hoping someone can help. I'm trying to speed up the process of filling a webform that must be completed dozens or hundreds of times with information stored in excel.
To do this, I need one button to open an IE window and navigate to a certain website's login page (I've figured this bit out). The user can then log in and navigate to the form that needs to be filled. Then, I'd like the user to be able to return to the excel page, click another button, which will automatically fill several drop downs and text boxes.
Within Excel, I already have some code to allow the user to search for the particular set of information that needs to go to the form, so all they should have to do is click the button to transfer it over. The first bit of the code is just this:
Public IE As Object
Public Sub OpenIE()
Set IE = CreateObject("InternetExplorer.Application")
IE.Visible = True
IE.Navigate "loginpage url"
End Sub
Where I'm having trouble, however, is having a different function access the same IE window once the user has logged in and navigated to the form. Right now I've got this:
Sub FillMacro()
Dim sh As Object, oWin As Object
Set sh = CreateObject("Shell.Application")
For Each oWin In sh.Windows
If TypeName(oWin.document) = "HTMLDocument" Then
Set IE = oWin
Exit For
End If
Next
IE.Visible = True
Application.Wait (Now + TimeValue("00:00:01"))
IE.document.getElementById("idec").Value = "John"
Application.Wait (Now + TimeValue("00:00:01"))
IE.document.getElementById("idee").Value = "Smith"
End Sub
Most of that I've gotten from other posts on this forum, and while I'm a bit of a novice at this, the problem seems to be that for some reason VBA can't find the text boxes with the id of LastName or FirstName. What's more, the IE.Visible = True doesn't bring the IE window back to the foreground, so I'm trying to find the proper line to do that. When I try to run the code, I get an "Object Required" error at:
IE.document.getElementById("idec").Value = "John"
I've tried searching this site and will continue to look, but any help in the meantime would be greatly appreciated!
On the Internet Explorer page, here is the line for the first text box I'm trying to fill:
<input name="componentListPanel:componentListView:1:component:patientLastNameContainer:patientLastName" class="input300" id="idec" type="text" maxlength="60" value="">
Why not automate logging process as well? Login could be stored in Excel and its value read by macro from cell.
As Tim Williams suggests, if there is Iframe on website, use (for me it works only with contentwindow included):
IE.document.getElementById("iFrameIdHere").contentwindow.document.getEleme‌ntById("idec").Value = "John"
Instead of Application.Wait use:
Do Until IE.ReadyState = 4 And IE.Busy = False
DoEvents
Loop
It will save you a lot of time when page loads fast and prevent errors when loading exceeds wait time. Use it ONLY after page reloads (meaning after navigating or anything what causes page reloads, especially .click on HTML elements.
Use early binding, it's a bit faster than creating objects. It can increase performance by a few percent based on page loading speed, the faster pages load, the bigger increase.
Set IE = New InternetExplorer
Finally, you can toggle loading pictures, depending on whether you need to download images from website.
Public Sub ShowPictures(ByVal EnabledStatus As Boolean)
Public ScrapingCancelled as Boolean
Dim obj_Shell
Dim v_Result As Variant
Set obj_Shell = CreateObject("WScript.Shell")
'Reads the registry key that determines whether 'Show pictures' Internet Explorer advanced setting is enabled
v_Result = obj_Shell.RegRead("HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\Display Inline Images")
Select Case v_Result
Case "yes" 'Pictures are displayed
If EnabledStatus = False Then _
obj_Shell.RegWrite "HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\Display Inline Images", "no", "REG_SZ"
Case "no" 'Pictures are not displayed
If EnabledStatus = True Then _
obj_Shell.RegWrite "HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\Display Inline Images", "yes", "REG_SZ"
Case Else
ScrapingCancelled = True
End Select
End Sub
No images loaded:
ShowPictures (0)
Images loaded:
ShowPictures (1)
A good practice is to set value to 1 in the end of macro.

Explorer thread blocked when getting property of InternetExplorer object during event

I'm looking for help (or suggestions/leads) on the following issue.
This console app:
looks for an InternetExplorer object,
when found, creates a new ListenerExplorer object
in which an event handler is activated (TitleChange)
Now, when you run this application while an explorer (not IE, but a general explorer.exe) is active and you navigate to a folder (this triggers a TitleChange event) the explorer thread is blocked. It is not possible to navigate further in explorer until the VB app is closed.
This pause occurs in the TitleChange sub, on the line
Console.WriteLine("URL=" + objIE.LocationURL)
Getting the LocationURL property outside of the event works fine. Within the event, it blocks the explorer thread.
What caught my attention is that in Visual Studio's immediate window, on a breakpoint on the line above, i'm unable to access the same property:
?objIE.LocationURL
Evaluation requires a thread to run temporarily. Use the Watch window to perform the evaluation.
Code:
Dim objIE As SHDocVw.InternetExplorer
Sub Main()
Dim objShellWindows As SHDocVw.ShellWindows = CreateObject(“Shell.Application”).Windows
If (objShellWindows.Count > 0) Then
objIE = objShellWindows.Item(0)
AddHandler objIE.TitleChange, AddressOf TitleChange
'wait for events until a key is pressed by the user
Do
If Not (Console.ReadKey.KeyChar = "") Then Exit Do
Loop
End If
End Sub
Private Sub TitleChange(title As String)
Console.WriteLine("TitleChange: " + title) 'works fine
Console.WriteLine("URL=" + objIE.LocationURL) 'blocks the explorer thread!
End Sub
I've Googled for days, but can't fix it... Any help would be greatly appreciated! Thanks
A console application needs a message loop.
Solved by creating a Windows Forms Application project in MS Visual Studio instead of a Console Application project.

VB 2013 Application Out of Memory

I'm new to VB but recently created my first working app :) Anyway it just compresses files and little bit more. The latest thing that I added was a marquee style progress bar to animate while the operation was in progress and stop when it ends and the user can do the next zip operation. The progress bar wasn't updating, so I used a background worker to do the actual task while the button click just did the animation. Since then I've notcied serious degredation in the app. It struggles to load. I even got an out of memory error. Not sure if the background worker is related, but I thought I'd mention as it was the last update. Has anyone experienced anything similar? If I can provide and specific info, please ask me for it! Many thanks.
UPDATE: So I understand that I'm not using the BGWorker correctly. I will change that. But I found even with that removed, I still had issues. So I created a new form and started adding in bits of my code one by one. Anyway, I fell at the first hurdle with my form load sub. So I added that in slowly. I found that when ever I have any statements that load a variable from settings for persistent settings that the app falls over. Below is my code. Can anyone see what's up?????
UPDATE: I've found that if I load from settings the memory useage shoots up. I tried this too with saving settins on form closed. Below is the error received. The same out of memory occurs when trying to load settings too. I never experienced this on the first form I created. So perhaps I have missed some settings on the second, because the implementation in the code hasn't changed.
System.Configuration.ConfigurationErrorsException: Failed to save settings: An error occurred executing the configuration section handler for userSettings/Backup_Tool.My.MySettings. ---> System.Configuration.ConfigurationErrorsException: An error occurred executing the configuration section handler for userSettings/Backup_Tool.My.MySettings. ---> System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown
This is when I added in the code below:
Private Sub Form1_Closed(sender As Object, e As EventArgs) Handles MyBase.FormClosed
' TAB PAGE 1.
' Save controls to settings.
My.Settings.StartPathTextBox1 = StartPathTextBox1.Text
My.Settings.ZipPathTextBox1 = ZipPathTextBox1.Text
My.Settings.CopyPathTextBox1 = CopyPathTextBox1.Text
My.Settings.ZipSelectCheckBox1 = ZipSelectCheckBox1.Checked
My.Settings.CopySelectCheckBox1 = CopySelectCheckBox1.Checked
For Each s As String In StartNameListBox1.Items()
My.Settings.StartNameListBoxItems1.Add(s)
Next
For Each s As String In StartNameListBox1.SelectedItems()
My.Settings.StartNameListBoxSelectedItems1.Add(s)
Next
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' FORM 1.
' Initialise specialised string collections.
If My.Settings.StartNameListBoxItems1 Is Nothing Then
My.Settings.StartNameListBoxItems1 = _
New System.Collections.Specialized.StringCollection
End If
If My.Settings.StartNameListBoxSelectedItems1 Is Nothing Then
My.Settings.StartNameListBoxSelectedItems1 = _
New System.Collections.Specialized.StringCollection
End If
' TAB PAGE 1.
' Restore controls from saved settings.
StartPathTextBox1.Text() = My.Settings.StartPathTextBox1
ZipPathTextBox1.Text() = My.Settings.ZipPathTextBox1
CopyPathTextBox1.Text() = My.Settings.CopyPathTextBox1
ZipSelectCheckBox1.Checked = My.Settings.ZipSelectCheckBox1
CopySelectCheckBox1.Checked = My.Settings.CopySelectCheckBox1
For Each s As String In My.Settings.StartNameListBoxItems1()
StartNameListBox1.Items.Add(s)
Next
For Each s As String In My.Settings.StartNameListBoxSelectedItems1()
StartNameListBox1.SelectedItems.Add(s)
Next
' Decide controls initial states.
If StartNameListBox1.SelectedItems.Count = 0 Then
ZipSelectCheckBox1.Enabled = False
RunButton1.Enabled = False
End If
If ZipSelectCheckBox1.Checked = False Then
ZipPathTextBox1.Enabled = False
ZipBrowseButton1.Enabled = False
End If
If ZipPathTextBox1.Text = String.Empty Then
CopySelectCheckBox1.Enabled = False
End If
If CopySelectCheckBox1.Checked = False Then
CopyPathTextBox1.Enabled = False
CopyBrowseButton1.Enabled = False
End If
End Sub
It appears to be that you are only ever adding the current selections to the Settings collections. You might well clear the ListBox when they make new selections, but you do not do the same thing with the Settings Collections like My.Settings.StartNameListBoxItems1:
For Each s As String In StartNameListBox1.Items()
My.Settings.StartNameListBoxItems1.Add(s)
Next
The collection will have all the items in it from all the other times it has ever run already and you are now going to add more to it. Eventually you will have many, many, many items in it.
My.Settings.StartNameListBoxItems1.Clear ' REMOVE ALL OLD ITEMS
' Save just the current items to the collection
For Each s As String In StartNameListBox1.Items()
My.Settings.StartNameListBoxItems1.Add(s)
Next
use .Clear on both Collections

Kill Excel on Error

I am hoping you can help me here, in the past you all have been great. I have tried every variation of the kill script for killing excel from vb.net, to no avail.
First I can't post explicit code on here because it is my company's proprietary software, but I can tell you a few things. Also there are over 28,000 lines of code.
I am not using
Imports Excel = Microsoft.Office.Interop.Excel
due to the fact that we have to accommodate different variations of clients software. I am creating the new excel as an object as such
Dim XLObj As Object = CreateObject("Excel.Application")
I have seen this used on several other sites but the kill function they are using is when you save and then close it, which I'm not doing.
The error message I am getting says that "Com object that has been separated from its underlying RCW cannot be used". I'm not sure where this com object is because I have released the sheets, workbook and then the application.
Oh and I don't want to use the excel.kill() because if a client already has the excel open I don't want to kill it without saving it. I only want to kill the newly generated excel process that doesn't have a window open associated with it.
My questions are as follows
I need to be able to close the Excel application when/if the open fails. So say I am click a link and it opens the dialog box to select an Excel template to load but either the data from the database is corrupt or the sql statement is broken. The program throws and error and then Excel should close in the Task Manager. Unfortunately it doesn't close hence the problem.
is there a way to close only the newly created process id? I have tried to use the directions here but it doesn't work either. When I do that it gives me a different error "Value cannot be null Parameter name: o". The line that is throwing the error is on (from the link)
Marshal.FinalReleaseComObject(tempVar)
I only tried this because we are using the With on the XLObj. The With is in reference to the workbook itself so shouldn't it be released when I close the workbook? And being as I'm causing it to error on purpose at the moment it shouldn't reach the With statement anyway.
Is there a way to tell which com object is not closing?
Things I have tried:
This releaseObject that I found on the internet. (don't ask me where I've been through about 75 pages)
Private Sub releaseObject(ByRef obj As Object)
Try
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(obj)
If obj Is Nothing Then
Else
obj = Nothing
End If
Catch ex As Exception
If obj Is Nothing Then
Else
obj = Nothing
End If
Finally
GC.Collect()
GC.WaitForPendingFinalizers()
End Try
End Sub
This is used in conjunction with this function (which was pieced together from the many sites I have been on)
Public Sub CloseExcel(ByRef WorkBook As Object, ByRef Application As Object)
Dim xLSheet As Object = WorkBook.Sheets
For Each xLSheet In WorkBook.Sheets
If xLSheet IsNot Nothing Then
releaseObject(xLSheet)
End If
If xLSheet IsNot Nothing Then
Kill(xLSheet)
End If
Next
If WorkBook IsNot Nothing Then
WorkBook.Close(False)
End If
If WorkBook IsNot Nothing Then
Kill(WorkBook)
End If
releaseObject(WorkBook)
If Application IsNot Nothing Then
Application.Quit()
End If
If Application IsNot Nothing Then
Kill(Application)
End If
releaseObject(Application)
GC.Collect()
GC.WaitForPendingFinalizers()
Application.Quit()
End Sub
and because it is also referenced the Kill function
Public Sub Kill(ByRef obj As Object)
Try
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(obj)
Catch ex As Exception
MessageBox.Show("moduleExcel.Kill " & ex.Message)
Finally
obj = Nothing
End Try
End Sub
any help would be greatly appreciated.
Ok so for those of you having this exact same issue. I do have a solution for you. Yes the above code does work but for a few minor adjustments.
you need to take out all the code in the CloseExcel sub and place it EXACTLY where you want it to close. So if you want it to close if the program errors out, put after the catch statement. You cannot call a Sub and pass in your objects and expect it to kill the process.
you need a few bits above the opening of the new Excel process. and they are as follows.
'declare process for excel
Dim XLProc As Process
'loads the financials excel bookmarks
'this will be where you declare your new excel opbject
Dim XLObj As Object = CreateObject("Excel.Application")
'get window handle
Dim xlHWND As Integer = XLObj.hwnd
Dim ProcIDXL As Integer = 0
'get the process ID
GetWindowThreadProcessId(xlHWND, ProcIDXL)
XLProc = Process.GetProcessById(ProcIDXL)
and of course you will need the GetWindowThreadProcessId which I got from the link I included in the original question. I am posting it here so you don't have to search for it.
<System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True)> _
Private Function GetWindowThreadProcessId(ByVal hWnd As IntPtr, ByRef lpdwProcessId As Integer) As Integer
End Function
This code will only close the single process you have it associated with, it will not close other open Excel files. Our clients sometimes will have multiple files open and we don't want to close them without telling them. This KILLS the Excel process that was created at run time when the system Errors out.