Quitting the IE in the end of VBA function - windows

I implemented several functions which relies on downloading some information from some websites.
The simplest example of such a function is:
Public Function getSomething(webAddress As String)
Dim html As HTMLObjectElement
Set html = getWebContents(webAddress)
Set elems = html.body.getElementsByTagName(tagName)
...
End Function
The function for acquire data from websites is:
Public Function getWebContents(webAddress As String) As HTMLObjectElement
Dim ie As InternetExplorer
Dim html As HTMLDocument
Set ie = New InternetExplorer
ie.Visible = False
ie.Navigate webAddress
Do While ie.READYSTATE <> READYSTATE_COMPLETE
Application.StatusBar = "Trying ..."
DoEvents
Loop
Set getWebContents = ie.Document
'close down IE and reset status bar
'ie.Quit
Set ie = Nothing
Application.StatusBar = ""
End Function
The problem is that it seems that I need the line ie.Quit to be uncommented to close the IE instance. But when I uncomment ie.Quit the line
Set elems = html.body.getElementsByTagName(tagName)
generates errors.
It seems that I cannot use HTMLObjectElement returned by function getWebContents when IE has been quitted. How to deal with that? I could implement a try...finally block in getSomething function and open ie there and close in the finally block. However I have many functions of a similar nature and making many similar try...finally blocks seems a stupid idea.
Any thoughts?
Thanks!

You should define a procedure to handle the object lifetime from creation to destruction. You can then pass a reference for the object to the function.
Lastly, you can dispose the object even if an error occurs at any stange.
Public Sub Main()
On Error GoTo ErrProc
Dim ie As InternetExplorer
Set ie = New InternetExplorer
'....
Dim obj As Object
obj = getWebContents(ie, "url")
Leave:
ie.Quit
Set ie = Nothing
Set obj = Nothing
Application.StatusBar = ""
On Error GoTo 0
Exit Sub
ErrProc:
MsgBox Err.Description, vbCritical
Resume Leave
End Sub
Public Function getWebContents(ie As InternetExplorer, webAddress As String) As HTMLObjectElement
'...
End Function

You are keeping a pointer to the DOM in the html variable. If you close IE, you are pointing to something non-existing.
The simple answer is to close IE at the end of getSomething. In your case, this means that you have to restructure your code so that your IE variable is accessible from other places than in getWebContents

Related

Handling COM event cancelation in VBScript

I would like to write a script, that sends an E-Mail via our companys SMTP-Server using CDO.
First I tried to write an HTA-application for that purpose, but it became rather fiddly to make it cormfortable enough so that other people may handle it well (because of proper recipient resolving).
So now I try to use the regular Outlook-Mail mask to prepare the mail first and then catch the send-item event via VBScript to give it's content to my CDO script.
Right now, my code looks like this:
Dim OutlookApplication
Dim MailItem
Const olDiscard = 1
Const olMailItem = 0
Set OutlookApplication = WScript.CreateObject("Outlook.Application", "Outlook_")
Set MailItem = OutlookApplication.CreateItem(olMailItem)
MailItem.Display
'(...) some code to add recipients, subject, text, etc... depending on the given WScript.Arguments
While Not MailItem Is Nothing
'keep the script alive
WScript.Sleep 1
WEnd
Function CDOSendMessage()
'some code to send the data to our smtp server, return true if successfull
CDOSendMessage = True
End Function
Sub Outlook_ItemSend(byVal Item, Cancel)
If Item.body = MailItem.body Then 'Any more fail proof suggestions on how to check if it's the correct mailitem I'm handling with this event? While the script is alive, it fires for EVERY mail I send via outlook
Cancel = True
If CDOSendMessage() then
Set MailItem = Nothing
MailItem.Close olDiscard
Else
Cancel = False
MsgBox "Sending message via CDO failed."
End If
End If
End Sub
The main problem is, that Cancel = True simply does not work. Outlook will send my mail using my regular mail adress no matter what. Can you tell me, what I'm doing wrong please?
Thank you very much in advance!
Guido
The Cancel parameter must be declared with the ByRef modifier.
Updated code as requested:
Dim OutlookApplication
Dim MailItem
Dim CDODone: CDODone = False
Const olDiscard = 1
Const olMailItem = 0
Set OutlookApplication = WScript.CreateObject("Outlook.Application", "Outlook_")
Set MailItem = OutlookApplication.CreateItem(olMailItem)
MailItem.UserProperties.Add "CDOFlag", 20, false, false
MailItem.Display
'(...) some code to add recipients, subject, text, etc... depending on the given WScript.Arguments
While Not CDODone Is Nothing
'keep the script alive
WScript.Sleep 1
WEnd
MailItem.Close olDiscard
Function CDOSendMessage()
'some code to send the data to our smtp server, return true if successfull
CDOSendMessage = True
End Function
Sub Outlook_ItemSend(byVal Item, byRef Cancel)
If Not Item.UserProperties.Find(CDOFlag) Is Nothing Then
Cancel = True
If CDOSendMessage() then
CDODOne = True
Else
Cancel = False
MsgBox "Sending message via CDO failed."
WScript.Quit
End If
End If
End Sub

VBScript 80004005; Was working as intended for hours, suddenly is not

I am new to VBScripting. Sorry for any mistakes, or lack of necessary information. I will do my best to include everything I can to help you help me.
My problem is when I execute the script, I get the following error:
Line: 22
Char: 5
Error: Unspecified error
Code: 80004005
Source: (null)
What is strange is that I had been running the same script multiple times all day without any issue. Now when I run it, the error is displayed. Nothing in the script changed. I have tried rebooting, but that seems to have done nothing.
Here is the code:
Call Main
Function Main
Dim IE
Dim pin
Set IE = WScript.CreateObject("InternetExplorer.Application", "IE_")
Set objShellApp = CreateObject("Shell.Application")
Set IE2 = WScript.CreateObject("InternetExplorer.Application", "IE_")
pin=inputbox("Pin: ","Enter the pin to continue","")
IE.Visible = True
IE.Navigate "https://ps.hasdk12.org/admin/pw.html"
For Each objWindow in objShellApp.Windows
If LCase(objWindow.LocationName) = LCase("PowerSchool") Then
Set IE2 = objWindow
End If
WScript.Sleep (5)
Next
With IE2.Document
.getElementByID("fieldPassword").value = "username;" + pin
.getElementByID("btnEnter").click
End With
For Each objWindow in objShellApp.Windows
If LCase(objWindow.LocationName) = LCase("Start Page") Then
Set IE2 = objWindow
End If
WScript.Sleep (5)
Next
End Function
Most probably reasons why your script become faulty are variations in page loading time, or nuber of opened Shell Explorer and IE windows etc. All troubles because your script doesn't wait while IE loading page, it checks each Explorer window and just continues even if target window isn't found.
Try this code:
Call Main
Function Main()
Dim oIE
Dim sPin
Set oIE = WScript.CreateObject("InternetExplorer.Application", "IE_")
sPin = InputBox("pin: ","Enter the pin to continue", "")
oIE.Visible = True
oIE.Navigate "https://ps.hasdk12.org/admin/pw.html"
WaitIE oIE, "PowerSchool"
With oIE.Document
.getElementByID("fieldPassword").value = "username;" + sPin
.getElementByID("btnEnter").click
End With
WaitIE oIE, "PowerSchool"
End Function
Function WaitIE(oIE, sLocation)
Do Until (LCase(oIE.LocationName) = LCase(sLocation)) And (Not oIE.Busy) And (oIE.ReadyState = 4)
WScript.Sleep 5
Loop
End Function
I've removed second IE variable, why did you get IE2 via objShellApp.Windows? Maybe I miss something..? IMO you already have IE instance hence getting the same instance such way is not necessary, just control that instance you have. Also I've added separate function that waits IE to complete page loading.

"Declaration expected" in vbscript

I'm new to vbscript. I get the error
Declaration expected at get_html
At the bottom part of my code. I am actually trying to declare a value (which is a url) for the variable get_html. How can I resolve this?
Module Module1
Sub Main()
End Sub
Sub get_html(ByVal up_http, ByVal down_http)
Dim xmlhttp : xmlhttp = CreateObject("msxml2.xmlhttp.3.0")
xmlhttp.open("get", up_http, False)
xmlhttp.send()
Dim fso : fso = CreateObject("scripting.filesystemobject")
Dim newfile : newfile = fso.createtextfile(down_http, True)
newfile.write(xmlhttp.responseText)
newfile.close()
newfile = Nothing
xmlhttp = Nothing
End Sub
get_html _"http://www.somwwebsite.com", _"c:\downloads\website.html"
End Module
There are some syntax mistakes.
Module statement is not part of VBScript.
Underscores can lead to unexpected results. See http://technet.microsoft.com/en-us/library/ee198844.aspx (search for the word underscore on page)
You can't use parentheses when calling a Sub (e.g. xmlhttp.open is a sub, does not return anything). You have two main alternatives to calling a sub routine. sub_proc param1, param2 or Call sub_proc(param1, param2)
The assignment operator '=' is not enough for the objects. You should
use Set statement. It assigns object references to the
variables.
The response may be return as utf-8 encoded. But however FSO is not at peace with utf-8. Another option is to write the response as unicode (passing True as third parameter to CreateTextFile)
but the output size will be larger than it should be. Therefore I would prefer to use Stream object.
I've revised your code. Please consider.
'Requeired Constants
Const adSaveCreateNotExist = 1 'only creates if not exists
Const adSaveCreateOverWrite = 2 'overwrites or creates if not exists
Const adTypeBinary = 1
Sub get_html(ByVal up_http, ByVal down_http)
Dim xmlhttp, varBody
Set xmlhttp = CreateObject("msxml2.xmlhttp.3.0")
xmlhttp.open "GET", up_http, False
xmlhttp.send
varBody = xmlhttp.responseBody
Set xmlhttp = Nothing
Dim str
Set str = CreateObject("Adodb.Stream")
str.Type = adTypeBinary
str.Open
str.Write varBody
str.SaveToFile down_http, adSaveCreateOverWrite
str.Close
Set str = Nothing
End Sub
get_html "http://stackoverflow.com", "c:\downloads\website.html"
You probably want to move your call to get_html to be a call from within your Main subroutine (Sub Main()). For example:
Sub Main()
get_html _"http://www.somwwebsite.com", _"c:\downloads\website.html"
End Sub
AFAIK, you can't make function calls from directly within a module.

VB6: get onreadystate value from browser control

I'm trying to get the onreadystate value from the browser control do detect when the page has finished loading. Unfortunately, the event's .returnValue gets returned empty.
Here's my code:
Dim WithEvents m_doc As HTMLDocument
Private Sub Form_Load()
Set m_doc = WebBrowser1.Document
End Sub
Private Sub m_doc_onreadystatechange()
Dim m_event As IHTMLEventObj
Set m_event = m_doc.parentWindow.event
m_value = "'" & m_event.returnValue & "'"
MsgBox "onreadystatechange: " & m_value
End Sub
Any ideas on what's wrong?
If you want to use the HTMLDocument's events try
m_doc.createDocumentFromUrl "http://www.microsoft.com", ""
Otherwise you can use the WebBrowser control's event to detect when a document is completely loaded or call the Navigate or Navigate2 method and immediately loop while polling the WebBrowser.ReadyState
WebBrowse1.Navigate2 "http://www.microsoft.com"
Do While WebBrowser1.ReadyState <> READYSTATE_COMPLETE
DoEvents
Loop
Of course don't forget to add an error handler.

Overriding CreateObject Function in VBScript

I want to override the default CreateObject() function in VBScript with my own.
Basically this example in VB6:
http://www.darinhiggins.com/the-vb6-createobject-function/
I cannot figure out is this line:
Set CreateObject = VBA.CreateObject(Class$, ServerName$)
How do I refer to "VBA" in VBSript?
This quick test seems to work...
Function CreateObject(className, serverName)
'---- override the CreateObject
' function in order to register what
' object is being created in any error message
' that's generated
Dim source, descr, errNum
WScript.echo "In custom CreateObject"
If Len(serverName) > 0 Then
Set CreateObject = WScript.CreateObject(className, serverName)
Else
Set CreateObject = WScript.CreateObject(className)
End If
End Function
Dim fso
Set fso = CreateObject("Scripting.FileSystemObject", "")
path = fso.GetAbsolutePathName(".")
WScript.echo path
No guarantees! ;-)
I don't think you can override it so that all code will use it, only YOUR code.
In which case, it doesn't matter what it's called (unless you have tons of existing code you can't change). Can you call it CreateObjectEx() or ExCreateObject() or something like that? Have this function add all your error handling and such and then turn around and call the main/core CreateObject() method

Resources