Alternative to URLDownloadtofile when automating IE with VBA - vba

I have been using InternetExplorer.application with Excel VBA for quite a while with few issues. One problem I have is downloading a file from website. I can get as far as having the "Open/Save As" buttons appear but that is where I am stuck.
I've tried using URLDownloadToFile and it does not seem to work through the same session as the InternetExplorer.application objects that I have. It usually returns the HTML text for a webpage stating that authentication is required. If I have multiple browsers open and some of the old ones are already authenticated then it will download the file most of the time.
Is there a way to download the file using the InternetExplorer.application object itself? If not, is there some way I can associate the URLDownloadtofile function with the object that is already authenticated and logged into the website?
EDIT:
The code I've been using is:
IE2.navigate ("https://...")
strURL = "https://..."
strPath = "c:\..."
Ret = URLDownloadToFile(0, strURL, strPath, 0, 0)
I've also tried:
Do While IE2.Readystate <> 4
DoEvents
Loop
SendKeys "%S"
IE2.ExecWB OLECMDID_SAVEAS, OLECMDEXECOPT_DODEFAULT
And:
Dim Report As Variant
Report = Application.GetSaveAsFilename("c:\...", "Excel Files (*.xls), *.xls")
No success in any of these, except for the first one which sometimes saves the actual file, but sometimes saves the website that states the authentication error.
Thanks,
Dave

I have managed to solve similar issue with some JavaScript.
The first step is to make JavaScript download the content of the file into a binary array (it doesn't require another authentication once the user is already authenticated).
Then, I needed to pass this binary array back to VBA. I didn't know the other way, so I print the content of this array into a temporary DIV element (with JavaScript) as a string and then read it with VBA and convert it back to binary array.
Finally, I re-created the file from the given binary array by using ADODB.Stream class.
The time required to download a single file grows geometrically with the size of this file. Therefore, this method is not suitable for large files (> 3MB), since it tooks more than 5 minutes then to download a single file.
Below is the code to do that:
'Parameters:
' * ie - reference to the instance of Internet Explorer, where the user is already authenticated.
' * sourceUrl - URL to the file to be downloaded.
' * destinationPath - where the file should be saved.
'Be aware that the extension of the file given in [destinationPath] parameter must be
'consistent with the format of file being downloaded. Otherwise the function below will
'crash on the line: [.SaveToFile destinationPath, 2]
Public Function saveFile(ie As Object, sourceUrl As String, destinationPath As String)
Dim binData() As Byte
Dim stream As Object
'------------------------------------------------------------------------------------
binData = getDataAsBinaryArray(ie, sourceUrl)
Set stream = VBA.CreateObject("ADODB.Stream")
With stream
.Type = 1
.Open
.write binData
.SaveToFile destinationPath, 2
End With
End Function
Private Function getDataAsBinaryArray(Window As Object, Path As String) As Byte()
Const TEMP_DIV_ID As String = "div_binary_transfer"
'---------------------------------------------------------------------------------------------
Dim strArray() As String
Dim resultDiv As Object
Dim binAsString As String
Dim offset As Integer
Dim i As Long
Dim binArray() As Byte
'---------------------------------------------------------------------------------------------
'Execute JavaScript code created automatically by function [createJsScript] in
'the given Internet Explorer window.
Call Window.Document.parentWindow.execScript(createJsScript(TEMP_DIV_ID, Path), "JavaScript")
'Find the DIV with the given id, read its content to variable [binAsString]
'and then convert it to array strArray - it is declared as String()
'in order to make it possible to use function [VBA.Split].
Set resultDiv = Window.Document.GetElementById(TEMP_DIV_ID)
binAsString = VBA.Left(resultDiv.innerhtml, VBA.Len(resultDiv.innerhtml) - 1)
strArray = VBA.Split(binAsString, ";")
'Convert the strings from the [strArray] back to bytes.
offset = LBound(strArray)
ReDim binArray(0 To (UBound(strArray) - LBound(strArray)))
For i = LBound(binArray) To UBound(binArray)
binArray(i) = VBA.CByte(strArray(i + offset))
Next i
getDataAsBinaryArray = binArray
End Function
'Function to generate JavaScript code doing three tasks:
' - downloading the file with given URL into binary array,
' - creating temporary DIV with id equal to [divId] parameter,
' - writing the content of binary array into this DIV.
Private Function createJsScript(divId As String, url As String) As String
createJsScript = "(function saveBinaryData(){" & vbCrLf & _
"//Create div for holding binary array." & vbCrLf & _
"var d = document.createElement('div');" & vbCrLf & _
"d.id = '" & divId & "';" & vbCrLf & _
"d.style.visibility = 'hidden';" & vbCrLf & _
"document.body.appendChild(d);" & vbCrLf & _
"var req = null;" & vbCrLf & _
"try { req = new XMLHttpRequest(); } catch(e) {}" & vbCrLf & _
"if (!req) try { req = new ActiveXObject('Msxml2.XMLHTTP'); } catch(e) {}" & vbCrLf & _
"if (!req) try { req = new ActiveXObject('Microsoft.XMLHTTP'); } catch(e) {}" & vbCrLf & _
"req.open('GET', '" & url & "', false);" & vbCrLf & _
"req.overrideMimeType('text/plain; charset=x-user-defined');" & vbCrLf & _
"req.send(null);" & vbCrLf & _
"var filestream = req.responseText;" & vbCrLf & _
"var binStream = '';" & vbCrLf & _
"var abyte;" & vbCrLf & _
"for (i = 0; i < filestream.length; i++){" & vbCrLf & _
" abyte = filestream.charCodeAt(i) & 0xff;" & vbCrLf & _
" binStream += (abyte + ';');" & vbCrLf & _
"}" & vbCrLf & _
"d.innerHTML = binStream;" & vbCrLf & _
"})();"
End Function

How about something like this?
Public Sub OpenWebXLS()
' *************************************************
' Define Workbook and Worksheet Variables
' *************************************************
Dim wkbMyWorkbook As Workbook
Dim wkbWebWorkbook As Workbook
Dim wksWebWorkSheet As Worksheet
Set wkbMyWorkbook = ActiveWorkbook
' *************************************************
' Open The Web Workbook
' *************************************************
Workbooks.Open ("http://www.sportsbookreviewsonline.com/scoresoddsarchives/nba/nba%20odds%202015-16.xlsx")
' *************************************************
' Set the Web Workbook and Worksheet Variables
' *************************************************
Set wkbWebWorkbook = ActiveWorkbook
Set wksWebWorkSheet = ActiveSheet
' *************************************************
' Copy The Web Worksheet To My Workbook and Rename
' *************************************************
wksWebWorkSheet.Copy After:=wkbMyWorkbook.Sheets(Sheets.Count)
wkbMyWorkbook.Sheets(ActiveSheet.Name).Name = "MyNewWebSheet"
' *************************************************
' Close the Web Workbook
' *************************************************
wkbMyWorkbook.Activate
wkbWebWorkbook.Close
End Sub

Related

to add two hours of timer to move file based on date modified

Some of VBA Experts have helped me a lot and have fixed the code for me which moves one file one at a time but it first moves the oldest file in the folder. However here i have a complex situation which for now not resolving. i.e. I would like to add a timer of two hours i.e. the file should move after two hour.
e.g. If a file named "North_West data" whose modified time is 6:40 PM i would like the code to move it exactly after two hours. Similarly at the next run the next file which has to be moved has filed modified time e.g. 6:50 PM so the VBA code should actually move it exactly after two hours. this means each file should have automatic two hours delay timer in it, i hope i am able to clarify the query.
Function OldestFile(strFold As String) As String
Dim FSO As Object, Folder As Object, File As Object, oldF As String
Dim lastFile As Date: lastFile = Now
Set FSO = CreateObject("Scripting.FileSystemObject")
Set Folder = FSO.GetFolder(strFold)
For Each File In Folder.Files
If File.DateLastModified < lastFile Then
lastFile = File.DateLastModified: oldF = File.Name
End If
Next
OldestFile = oldF
End Function
Sub MoveOldestFile()
Dim FromPath As String, ToPath As String, fileName As String
FromPath = "E:\Source\"
ToPath = "E:\Destination\"
fileName = OldestFile(FromPath)
If Dir(ToPath & fileName) = "" Then
Name FromPath & fileName As ToPath & fileName
Else
MsgBox "File """ & fileName & """ already moved..."
End If
End Sub
You can check the previously resolved query here
Previous query
Please, try the next way. Basically, it uses a VBScript able to catch file creation event, which sends the created file name and the moment of creation to a workbook which should be open all the time.
Create a VBScript and name it "FolderMonitor.vbs". To do that, please copy the next code in an empty Notepad window:
Dim oExcel, strWB, nameWB, wb
strWB = "C:\Teste VBA Excel\Folder monitor.xlsm" 'use here the path of the waiting workbook!
nameWB = Left(strWB, InStr(StrReverse(strWB), "\") - 1)
nameWB = Right(strWB, Len(nameWB))
Set objExcel = GetObject(,"Excel.Application")
Set wb = objExcel.Workbooks(nameWB)
if wb is nothing then wbscript.quit 'the necessary workbook is not open...
dim strComputer, strDirToMonitor, strTime, objWMIService, colMonitoredEvents, objEventObject, MyFile
strComputer = "."
'# WMI needs two backslashes (\\) as path separator and each of it should be excaped.
'# So, you must use 4 backslashes (\\\\) as path separator!
strDirToMonitor = "C:\\\\test\\\\test" 'use here your path
'# Monitor Above every 10 secs...
strTime = "10"
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colMonitoredEvents = objWMIService.ExecNotificationQuery _
("SELECT * FROM __InstanceOperationEvent WITHIN " & strTime & " WHERE " _
& "Targetinstance ISA 'CIM_DirectoryContainsFile' and " _
& "TargetInstance.GroupComponent= " _
& "'Win32_Directory.Name=" & Chr(34) & strDirToMonitor & Chr(34) & "'")
Do While True
Set objEventObject = colMonitoredEvents.NextEvent()
Select Case objEventObject.Path_.Class
Case "__InstanceCreationEvent"
MyFile = StrReverse(objEventObject.TargetInstance.PartComponent)
' Get the string to the left of the first \ and reverse it
MyFile = (StrReverse(Left(MyFile, InStr(MyFile, "\") - 1)))
MyFile = Mid(MyFile, 1, Len(MyFile) - 1)
'send the information to the waiting workbook:
objExcel.Application.Run "'" & strWB & "'!GetMonitorInformation", Array(MyFile,Now)
' some other events can be used starting from here...
End Select
Loop
And save it as stated above. But take care to not save it as "FolderMonitor.vbs.txt". In order to avoid that, when saving you should change 'Save as typefrom defaultText documents (.txt)toAll files (.*)`!
In order to make the following code working as it is, you should create a folder named "VBScript" in the folder where the workbook running the code exists and place the above VBScript inside!
Copy the next code in a standard module of a xlsm workbook. In order to be called by the above script, as it is, you should name it "Folder monitor.xlsm":
Option Explicit
Private Const ourScript As String = "FolderMonitor.vbs"
Private Const fromPath As String = "E:\Source\"
Sub startMonitoring()
Dim strVBSPath As String
strVBSPath = ThisWorkbook.Path & "\VBScript\" & ourScript
TerminateMonintoringScript 'to terminate monitoring script, if running..
Shell "cmd.exe /c """ & strVBSPath & """", 0
End Sub
Sub TerminateMonintoringScript()
Dim objWMIService As Object, colItems As Object, objItem As Object, Msg As String
Set objWMIService = GetObject("winmgmts:\\" & "." & "\root\CIMV2")
Set colItems = objWMIService.ExecQuery("SELECT * FROM Win32_Process", "WQL", 48)
For Each objItem In colItems
If objItem.Caption = "wscript.exe" Then
'// msg Contains the path of the exercutable script and the script name
On Error Resume Next
Msg = objItem.CommandLine 'for the case of null
On Error GoTo 0
'// If wbscript.exe runs the monitoring script:
If InStr(1, Msg, ourScript) > 0 Then
Debug.Print "Terminate Wscript process..."
objItem.Terminate 'terminate process
End If
End If
Next
Set objWMIService = Nothing: Set colItems = Nothing
End Sub
Sub GetMonitorInformation(arr As Variant)
'call DoSomething Sub after 2 hours (now IT WILL RUN AFTER 1 MINUTE, for testing reasons...)
'for running after 2 hours you should change "00:01:00" in "02:00:00":
arr(0) = Replace(arr(0), "'", "''") 'escape simple quote (') character'
Application.OnTime CDate(arr(1)) + TimeValue("00:01:00"), "'DoSomething """ & CStr(arr(0)) & """'"
Debug.Print "start " & Now 'just for testing (wait a minute...)
'finaly, this line should be commented.
End Sub
Sub DoSomething(strFileName As String)
Const toPath As String = "E:\Destination\"
If Dir(toPath & strFileName) = "" Then
Name fromPath & strFileName As toPath & strFileName
Debug.Print strFileName & " moved from " & fromPath & " to " & toPath 'just for testing...
Else
MsgBox "File """ & toPath & strFileName & """ already exists in this location..."
End If
End Sub
a. You firstly should run "startMonitoring" Sub. It can be called from the Workbook_Open event.
b. Copy files in the monitored folder and check if they are copied as it should. Note that the code as it is move it after a minute. It is commented to exactly show what and how it can be changed...

Optical Character Recognition from Access via VBA

I wish to OCR a few JPEGs (I can convert on the fly with iview).
I get:
Method 'OCR' of object 'IImage' failed
My code isn't perfect yet as I am focused on getting the .ocr method to function.
The images are photos and contain only a few characters. I could use a barcode reader, but those are hard to find free.
Public Function OCRtest(strTempImg)
pXname = "ocrTest"
On Error GoTo err_hand
Dim miDoc As Object
Dim miWord As MODI.Word
Dim strWordInfo As String
Set miDoc = CreateObject("MODI.Document")
miDoc.Create strTempImg
' Perform OCR.
miDoc.Images(0).ocr
' Retrieve and display word information.
Set miWord = miDoc.Images(0).Layout.Words(2)
strWordInfo = _
"Id: " & miWord.id & vbCrLf & _
"Line Id: " & miWord.LineId & vbCrLf & _
"Region Id: " & miWord.RegionId & vbCrLf & _
"Font Id: " & miWord.FontId & vbCrLf & _
"Recognition confidence: " & _
miWord.RecognitionConfidence & vbCrLf & _
"Text: " & miWord.Text
Set miWord = Nothing
Set miDoc = Nothing
OCRtest = strWordInfo
Return
Exit Function
err_hand:
Call CStatus(Error, 504, Err.Number, Err.description, strTempImg)
End Function
If you use MS Office 2010, you need install MODI firstly.
Then, you need to add reference to: Microsoft Office Document Imaging 1x.0 Type Library and you'll be able to use this code:
Sub OCRReader()
Dim doc1 As MODI.Document
Dim inputFile As String
Dim strRecText As String
Dim imageCounter As Integer
inputFile = Application.GetOpenFilename
strRecText = ""
Set doc1 = New MODI.Document
doc1.Create (inputFile)
doc1.OCR ' this will ocr all pages of a multi-page tiff file
For imageCounter = 0 To (doc1.Images.Count - 1) ' work your way through each page of results
strRecText = strRecText & doc1.Images(imageCounter).Layout.Text ' this puts the ocr results into a string
Next
fnum = FreeFile()
Open "C:\Test\testmodi.txt" For Output As fnum
Print #fnum, strRecText
Close #fnum
doc1.Close
End Sub
Above code comes from: https://www.mrexcel.com/forum/excel-questions/358499-read-data-tiff-file-using-modi-ocr-vba.html

Adding page numbers to pdf through VBA and Acrobat IAC

I am trying to do the following thing from Excel vba:
Export certain worksheets to pdf
Take an existing pdf document and insert it in the newly generated pdf at a specific place (not necessarily at the end or at the beginning)
Number the pages of the merged pdf, omitting two title pages
I already figured out the first step. For the second and third step, I have Adobe Acrobat XI Pro at my disposal. Since I want to do this in one go from vba, I have downloaded the Acrobat SDK. From some quick Googling, I think I should be able to figure out the second step now, using the IAC, but the third step (oddly) seems the most difficult. Any suggestions would be welcome.
Best,
NiH
In the meantime, I found a solution for adding page numbers. For anyone who might be interested, here's an example of how it can be done:
Sub addPageNumbers()
Dim acroApp As Acrobat.acroApp
Dim myDocument As Acrobat.AcroPDDoc
Dim jso As Object
Dim strPath As String
Dim strFileName As String
Dim intPages As Integer
Dim i As Integer
Set acroApp = CreateObject("AcroExch.App")
Set myDocument = CreateObject("AcroExch.PDDOc")
strPath = "C:\"
strFileName = "myDoc.pdf"
'Open file and load JSObject
Set myDocument = CreateObject("AcroExch.PDDOc")
myDocument.Open (strPath & strFileName)
Set jso = myDocument.GetJSObject
' get number of pages
intPages = myDocument.GetNumPages
'Write page numbers to all pages
For i = 1 To intPages
jso.addWatermarkFromText _
cText:=Str(i) & " ", _
nTextAlign:=1, _
nHorizAlign:=2, _
nVertAlign:=4, _
nStart:=i - 1, _
nEnd:=i - 1
Next i
'Save document
Call myDocument.Save(1, strPath & strFileName)
'Clean up
Set jso = Nothing
Call acroApp.CloseAllDocs
Set myDocument = Nothing
Call acroApp.Exit
Set acroApp = Nothing
End Sub
Keep in mind that you need to have Acrobat (not only the reader) installed on your computer, and the reference to Acrobat has to be enabled in the vba editor.
I did not add error handling; obviously you should.
More info on the addwatermarkFromText method can be found here
Best regards,
NiH
Here another method to do it. I use the add field method from acrobat js. The "ExecuteThisJavaScript" method has the advantage that you can use js without translation to js-object.
The following example - I published somewhere already - add Date, filename and pageNo as footer to a pdf. It's written in VBS but can also used as vba without changes.
Best regards, Reinhard
File = "D:\Test.pdf"
Set App = CreateObject("Acroexch.app") '//start acrobat
app.show '//show Acrobat or comment out for hidden mode
Set AVDoc = CreateObject("AcroExch.AVDoc")
Set AForm = CreateObject("AFormAut.App") '//get AFormAPI to execute js later
If AVDoc.Open(File,"") Then
'//write JS-Code on a variable
Ex = " // set Date, filename and PageNo as footer "&vbLF _
& " var Box2Width = 50 "&vbLF _
& " for (var p = 0; p < this.numPages; p++) "&vbLF _
& " { "&vbLF _
& " var aRect = this.getPageBox(""Crop"",p); "&vbLF _
& " var TotWidth = aRect[2] - aRect[0] "&vbLF _
& " { var bStart=(TotWidth/2)-(Box2Width/2) "&vbLF _
& " var bEnd=((TotWidth/2)+(Box2Width/2)) "&vbLF _
& " var fp = this.addField(String(""xftPage""+p+1), ""text"", p, [bStart,30,bEnd,15]); "&vbLF _
& " fp.value = ""Page: "" + String(p+1)+ ""/"" + this.numPages; "&vbLF _
& " fp.textSize=6; fp.readonly = true; "&vbLF _
& " fp.alignment=""center""; "&vbLF _
& " } "&vbLF _
& " } "
'//Execute JS-Code
AForm.Fields.ExecuteThisJavaScript Ex
msgBox("Done")
end if
Set AVDoc = Nothing
Set APP = Nothing

MSXML2.DOMDocument load function fails in VBA

I've been struggling with the below issue for a while now and couldn't find the solution yet.
There is an iShare page with an XML file that I want to download using VBA code, then later process the XML file and save into MS Access database.
I've been using the below code for about 4 years now, it worked perfectly without any issues. But suddenly it stopped working this week.
Any ideas why?
the code:
Private Function GetRequests() As Boolean
On Error GoTo ErrHandler
Dim oDoc As MSXML2.DOMDocument
Dim Url As String
Dim sFileName As String
Set oDoc = New MSXML2.DOMDocument
oDoc.async = False
Url = cUrlDatabase & "/" & cApplicationName & "/In/" & cReqXmlFile
UpdateStatus "Loading " & cReqXmlFile
If Not oDoc.Load(Url) Then
c_sLastError = "Could not load XML " & Url
GoTo EndProc
End If
sFileName = sPath & "\Data\requests.xml"
oDoc.Save sFileName
GetRequests = True
End Function
The code fails at the oDoc.Load(Url) part, it comes back false.
Here's an example of how to gather error details:
Dim xDoc As MSXML.DOMDocument
Set xDoc = New MSXML.DOMDocument
If xDoc.Load("C:\My Documents\cds.xml") Then
' The document loaded successfully.
' Now do something intersting.
Else
' The document failed to load.
Dim strErrText As String
Dim xPE As MSXML.IXMLDOMParseError
' Obtain the ParseError object
Set xPE = xDoc.parseError
With xPE
strErrText = "Your XML Document failed to load" & _
"due the following error." & vbCrLf & _
"Error #: " & .errorCode & ": " & xPE.reason & _
"Line #: " & .Line & vbCrLf & _
"Line Position: " & .linepos & vbCrLf & _
"Position In File: " & .filepos & vbCrLf & _
"Source Text: " & .srcText & vbCrLf & _
"Document URL: " & .url
End With
MsgBox strErrText, vbExclamation End If
Set xPE = Nothing
End If
Example taken from here.
For other people finding this post:
The xml parser by now has implemented different error types (see here).
You would have to use the following code
Set objXML = CreateObject("Msxml2.DOMDocument.6.0")
ObjXML.async=true
objXML.load "/path/to/xml"
If objXML.parseError.errorCode <> 0 Then
MsgBox "Error was " + objXML.parseError.reason
End If
This should help you debug your .xml file.
For anyone else struggling with this, I found this error to be caused by text encoded in a format which could not be parsed in VBA (some weird E symbol). The objXML was nothing after the .load. I'm sure there are many possible causes, but I'll share what I found in case this helps someone. Thanks to the guys above for the error handling routines.

Upload Excel xlsm file to php script using VBA

I would like to upload an Excel xlsm file to a php script from VBA. I found the following code:
Dim WinHttpReq As Object
Set WinHttpReq = CreateObject("WinHttp.WinHttpRequest.5.1")
Dim strURL As String
Dim StrFileName As String
Dim FormFields As String
Dim path As String
Dim name As String
StrFileName = "c:\temp\ctc1output.xls"
strURL = "http://www.tri-simulation.com/P3/"
WinHttpReq.Open "POST", strURL, False
' Set the header
WinHttpReq.SetRequestHeader "Content-Type", "application/x-www-form-urlencoded"
FormFields = """fileulp=" & StrFileName & """"
FormFields = FormFields + "&"
FormFields = FormFields + """sfpath=P3"""
WinHttpReq.Send FormFields
' Display the status code and response headers.
MsgBox WinHttpReq.GetAllResponseHeaders
MsgBox WinHttpReq.ResponseText
Should I handle the file as a binary file or another type of file?
Can I upload the file while it is still open (I want to upload the file from which the VBA is running from)?
I am not sure if I'm on the right track.
I'm also not sure about what the headers and form fields should be.
Thx for any help.
You won't need to base64 encode anything. Follow the sample code you have found but before preparing the file (before '---prepare body comment) just add your other texts (form entries) like this
sPostData = sPostData & "--" & STR_BOUNDARY & vbCrLf & _
"Content-Disposition: form-data; name=""" & Name & """"
sPostData = sPostData & vbCrLf & vbCrLf & _
pvToUtf8(Value) & vbCrLf
... where Name and Value are the designed name and the actual text that you want to include in service request. For the function pvToUtf8 implementation take a look at this Google Cloud Print service connector. The snippet above is taken from pvRestAddParam function of the same connector.