Connecting Excel spreadsheet with Google maps while using VBA - vba

I have found a code on which helps me export both travel time and a distance between 2 locations from Google Maps, while using Excel spreadsheet.
It works fine, but one thing I am missing is an opportunity to add an in-between point. Do you think it is doable to modify the code so I could have 3th cell with in-between point?
Please find code below:
' Usage :
' GetGoogleTravelTime (strFrom, strTo) returns a string containing journey duration : hh:mm
' GetGoogleDistance (strFrom, strTo) returns a string containing journey distance in either miles or km (as defined by strUnits)
' GetGoogleDirections (strFrom, strTo) returns a string containing the directions
'
' where strFrom/To are address search terms recognisable by Google
' i.e. Postcode, address etc.
'
' by Desmond Oshiwambo
Const strUnits = "metric" ' imperial/metric (miles/km)
Function CleanHTML(ByVal strHTML)
'Helper function to clean HTML instructions
Dim strInstrArr1() As String
Dim strInstrArr2() As String
Dim s As Integer
strInstrArr1 = Split(strHTML, "<")
For s = LBound(strInstrArr1) To UBound(strInstrArr1)
strInstrArr2 = Split(strInstrArr1(s), ">")
If UBound(strInstrArr2) > 0 Then
strInstrArr1(s) = strInstrArr2(1)
Else
strInstrArr1(s) = strInstrArr2(0)
End If
Next
CleanHTML = Join(strInstrArr1)
End Function
Public Function formatGoogleTime(ByVal lngSeconds As Double)
'Helper function. Google returns the time in seconds, so this converts it into time format hh:mm
Dim lngMinutes As Long
Dim lngHours As Long
lngMinutes = Fix(lngSeconds / 60)
lngHours = Fix(lngMinutes / 60)
lngMinutes = lngMinutes - (lngHours * 60)
formatGoogleTime = Format(lngHours, "00") & ":" & Format(lngMinutes, "00")
End Function
Function gglDirectionsResponse(ByVal strStartLocation, ByVal strEndLocation, ByRef strTravelTime, ByRef strDistance, ByRef strInstructions, Optional ByRef strError = "") As Boolean
On Error GoTo errorHandler
' Helper function to request and process XML generated by Google Maps.
Dim strURL As String
Dim objXMLHttp As Object
Dim objDOMDocument As Object
Dim nodeRoute As Object
Dim lngDistance As Long
Set objXMLHttp = CreateObject("MSXML2.XMLHTTP")
Set objDOMDocument = CreateObject("MSXML2.DOMDocument.6.0")
strStartLocation = Replace(strStartLocation, " ", "+")
strEndLocation = Replace(strEndLocation, " ", "+")
strURL = "http://maps.googleapis.com/maps/api/directions/xml" & _
"?origin=" & strStartLocation & _
"&destination=" & strEndLocation & _
"&sensor=false" & _
"&units=" & strUnits 'Sensor field is required by google and indicates whether a Geo-sensor is being used by the device making the request
'Send XML request
With objXMLHttp
.Open "GET", strURL, False
.setRequestHeader "Content-Type", "application/x-www-form-URLEncoded"
.Send
objDOMDocument.LoadXML .ResponseText
End With
With objDOMDocument
If .SelectSingleNode("//status").Text = "OK" Then
'Get Distance
lngDistance = .SelectSingleNode("/DirectionsResponse/route/leg/distance/value").Text ' Retrieves distance in meters
Select Case strUnits
Case "imperial": strDistance = Round(lngDistance * 0.00062137, 1) 'Convert meters to miles
Case "metric": strDistance = Round(lngDistance / 1000, 1) 'Convert meters to miles
End Select
'Get Travel Time
strTravelTime = .SelectSingleNode("/DirectionsResponse/route/leg/duration/value").Text 'returns in seconds from google
strTravelTime = formatGoogleTime(strTravelTime) 'converts seconds to hh:mm
'Get Directions
For Each nodeRoute In .SelectSingleNode("//route/leg").ChildNodes
If nodeRoute.BaseName = "step" Then
strInstructions = strInstructions & nodeRoute.SelectSingleNode("html_instructions").Text & " - " & nodeRoute.SelectSingleNode("distance/text").Text & vbCrLf
End If
Next
strInstructions = CleanHTML(strInstructions) 'Removes MetaTag information from HTML result to convert to plain text.
Else
strError = .SelectSingleNode("//status").Text
GoTo errorHandler
End If
End With
gglDirectionsResponse = True
GoTo CleanExit
errorHandler:
If strError = "" Then strError = Err.Description
strDistance = -1
strTravelTime = "00:00"
strInstructions = ""
gglDirectionsResponse = False
CleanExit:
Set objDOMDocument = Nothing
Set objXMLHttp = Nothing
End Function
Function getGoogleTravelTime(ByVal strFrom, ByVal strTo) As String
'Returns the journey time between strFrom and strTo
Dim strTravelTime As String
Dim strDistance As String
Dim strInstructions As String
Dim strError As String
If gglDirectionsResponse(strFrom, strTo, strTravelTime, strDistance, strInstructions, strError) Then
getGoogleTravelTime = strTravelTime
Else
getGoogleTravelTime = strError
End If
End Function
Function getGoogleDistance(ByVal strFrom, ByVal strTo) As String
'Returns the distance between strFrom and strTo
'where strFrom/To are address search terms recognisable by Google
'i.e. Postcode, address etc.
Dim strTravelTime As String
Dim strDistance As String
Dim strError As String
Dim strInstructions As String
If gglDirectionsResponse(strFrom, strTo, strTravelTime, strDistance, strInstructions, strError) Then
getGoogleDistance = strDistance
Else
getGoogleDistance = strError
End If
End Function
Function getGoogleDirections(ByVal strFrom, ByVal strTo) As String
'Returns the directions between strFrom and strTo
'where strFrom/To are address search terms recognisable by Google
'i.e. Postcode, address etc.
Dim strTravelTime As String
Dim strDistance As String
Dim strError As String
Dim strInstructions As String
If gglDirectionsResponse(strFrom, strTo, strTravelTime, strDistance, strInstructions, strError) Then
getGoogleDirections = strInstructions
Else
getGoogleDirections = strError
End If
End Function
Thank you,
Przemek

Related

Delete special character SOH with RegExp

For an Access development I used GetOpenFilename function as explained here
But lpstrTitle is set with special character SOH. To delete SOH, I create a regExp function
Public Function RegParse(sStr As String) '----> sStr is lpstrTitle from getopenfiled
Dim oRegex As New RegExp
sPattern = "^.*?(?=\x01)" '--> Failed on .Test
'sPattern = ^[^\x01]* '--> successful.Test but SOH still there
'sPattern = (^.*)v(.*) '-->Ok but v deleted
.Replace(sStr, "$1")
With oRegex
.IgnoreCase = True
.pattern = sPattern
.Global = False
If .Test(sStr) Then
sStr1 = .Execute(sStr)(0)
End With
End Function
But sStr1 is still with SOH characters with sPattern = ^[^\x01]*
And command sStr1 = .replace(sStr1, “$1”) is impossible because sPattern = "^.*?(?=\x01) failed in .test
Thanks by advance for any help
Just do a simple find and replace with nothing if you want to remove a specific character:
sStr1 = Replace(sStr1, Chr(1), "", Compare := vbBinaryCompare)
vbBinaryCompare makes the find and replace binary, to avoid weirdness with control characters.
My code in a module
Public Declare Function GetOpenFileName Lib "comdlg32.dll" Alias "GetOpenFileNameA" (pOpenfilename As OPENFILENAME) As Long
Public Type OPENFILENAME
lStructSize As Long
hwndOwner As Long
hInstance As Long
lpstrFilter As String
lpstrCustomFilter As String
nMaxCustFilter As Long
nFilterIndex As Long
lpstrFile As String
nMaxFile As Long
lpstrFileTitle As String
nMaxFileTitle As Long
lpstrInitialDir As String
lpstrTitle As String
flags As Long
nFileOffset As Integer
nFileExtension As Integer
lpstrDefExt As String
lCustData As Long
lpfnHook As Long
lpTemplateName As String
End Type
Public typOpenFile As OPENFILENAME
Public Function RegParse(psStr As String, psPattern As String) As String
Dim oRegex As New RegExp
Dim sStr As String, sPattern As String
sStr2 = Replace(psStr, Chr(1), "", Compare:=vbBinaryCompare)
Debug.Print sStr2
sPattern = psPattern
With oRegex
.Global = True 'restricting regex to find only first match.
.IgnoreCase = True 'ignoring cases while regex engine performs the search.
.Pattern = sPattern
If .Test(psStr) Then 'Testing if the pattern matches or not
sStr = .Execute(psStr)(0) 'will provide the String which matches with Regex
sStr1 = .Replace(psStr, "$1") '.Replace function will replace the String with whatever is in the first set of braces - $X
End If
End With
Exit_:
RegParse = sStr
Exit Function
Err_:
sStr = ""
gsMsg = Err.Number & " : " & Err.Description & Chr(13) & "Process aborted"
MsgBox gsMsg, vbCritical, "Error message"
GoTo Exit_
End Function
Public Function mfOpenFileDialog(psPathDir As String, Optional psFileCrit As String) As Boolean
Dim lReturn As Long
Dim strFilter As String
Dim sFileSelected As String
Dim bOk As Boolean
bOk = True
typOpenFile.lStructSize = Len(typOpenFile)
strFilter = "Text File (*" & psFileCrit & "*.csv)" & Chr(0) & "*" & psFileCrit & "*.csv" & Chr(0) '--> Define your filter here
With typOpenFile
.lpstrFilter = strFilter
.nFilterIndex = 1
.lpstrFile = String(257, 0)
.nMaxFile = Len(.lpstrFile) - 1
.lpstrFileTitle = .lpstrFile
.nMaxFileTitle = .nMaxFile
.lpstrInitialDir = psPathDir
.lpstrTitle = "My FileFilter Open"
.flags = 0
End With
If GetOpenFileName(typOpenFile) = 0 Then
MsgBox "No file selected", vbCritical, "Error message"
bOk = False
End If
Exit_:
mfOpenFileDialog = bOk
Exit Function
Err_:
bOk = False
gsMsg = "Function mfOpenFileDialog" & Chr(13) & Err.Number & Chr(13) & Err.Description
MsgBox gsMsg, vbCritical, "Error message"
GoTo Exit_
End Function
I call mfOpenFileDialog function from a click button event
sPathDefault = "c:\Extraction"
sFileCrit = "rapport_"
If mfOpenFileDialog(sPathDefault, sFileCrit) = False Then GoTo Exit_
sPattern = "(^.*?(?=\x01))(\x01*)"
sFileName = RegParse(typOpenFile.lpstrFile, sPattern)
That's all
In fact, the special characters are not chr(1) but chr(0) or x00
I tested with this function
Public Function mfShowChar(psStr As String)
Dim i As Integer
Dim arrChar() As Integer
For i = 1 To Len(psStr)
ReDim Preserve arrChar(i)
ArrChar(i) = Asc(Mid(psStr, i))
Next
End Function
And the both solutions work
sStr = Replace(typOpenFile.lpstrFile, Chr(0), vbNullString)
RegExp but with sPattern = "(^.?(?=\x00))(\x00)"

error 13 in Inputbox/ Switch case statement

I tried to a macro to get top 3 street addresses from a keyword and let the user choose one. However, I kept running into error 13( mismatched data type) on my "case 1" statement or in the last input box. Any help?I am relatively new to VBA.
Sub myTest2()
Dim xhrRequest As XMLHTTP60
Dim domDoc As DOMDocument60
Dim query As String
Dim myNodes As IXMLDOMNodeList
Dim myNode As IXMLDOMNode
Dim nNode As Integer
Dim re As Range
Dim result(2) As String
'allows user to select range
Set myValue = Application.InputBox(prompt:="Please select the list of addresses wether empty or not of the organizations", Type:=8)
Set myValueCol = Application.InputBox(prompt:="Please select the column with the names", Type:=8)
For Each re In myValue
If IsEmpty(re.Value) Or re.Value = vbNullString Then
query = Cells(re.Row, myValueCol.Column)
query = Replace(query, " ", "+")
query = Replace(query, ",", "%2C")
'You must acquire a google api key and enter it here
Dim googleKey As String
googleKey = "KEY_HERE" 'your api key here
'Send a "GET" request for place/textsearch
Set xhrRequest = New XMLHTTP60
xhrRequest.Open "GET", "https://maps.googleapis.com/maps/api/place/textsearch/xml?query=" & query & "&key=" & googleKey, False
xhrRequest.send
'Save the response into a document
Set domDoc = New DOMDocument60
domDoc.LoadXML xhrRequest.responseText
Set myNodes = domDoc.SelectNodes("//result/formatted_address")
For nNode = 0 To 2
Set myNode = myNodes(nNode)
If Not (myNode Is Nothing) Then
result(nNode) = myNode.Text
End If
If myNode Is Nothing Then
result(nNode) = "no additional adresses found"
End If
Next nNode
sinput = Application.InputBox(prompt:="1. " & result(0) & vbNewLine & "2. " & result(1) & vbNewLine & "3. " & result(2), Type:=1)
Select Case sinput
Case 1
re = result0
Case 2
re = result1
Case 3
re = result2
End Select
End If
Next re
End Sub
This is what finally worked for me. Thank you :)
Sub allowUserToChooseLocation()
Dim xhrRequest As XMLHTTP60
Dim domDoc As DOMDocument60
Dim query As String
Dim myNodes As IXMLDOMNodeList
Dim myNode As IXMLDOMNode
Dim nNode As Integer
Dim re As Range
Dim result(0 To 2) As String
'allows user to select range
Set myValue = Application.InputBox(prompt:="Please select the list of addresses wether empty or not of the organizations", Type:=8)
Set myValueCol = Application.InputBox(prompt:="Please select the column with the names", Type:=8)
For Each re In myValue
If IsEmpty(re.Value) Or re.Value = vbNullString Then
query = Cells(re.Row, myValueCol.Column)
query = Replace(query, " ", "+")
query = Replace(query, ",", "%2C")
'You must acquire a google api key and enter it here
Dim googleKey As String
googleKey = "Key_Here" 'your api key here
'Send a "GET" request for place/textsearch
Set xhrRequest = New XMLHTTP60
xhrRequest.Open "GET", "https://maps.googleapis.com/maps/api/place/textsearch/xml?query=" & query & "&key=AIzaSyAmkY6nbeMGS19t0jdcFQT_SxoFcm7LUdE", False
xhrRequest.send
'Save the response into a document
Set domDoc = New DOMDocument60
domDoc.LoadXML xhrRequest.responseText
Set myNodes = domDoc.SelectNodes("//result/formatted_address")
For nNode = 0 To 2
Set myNode = myNodes(nNode)
If Not (myNode Is Nothing) Then
result(nNode) = myNode.nodeTypedValue
End If
If (myNode Is Nothing) Then
result(nNode) = "No additonal addresses found"
End If
Next nNode
sinput = Application.InputBox(prompt:="1. " & result(0) & vbNewLine & "2. " & result(1) & vbNewLine & "3. " & result(2), Type:=1)
Select Case sinput
Case 1
re.Value = result(0)
Case 2
re.Value = result(1)
Case 3
re.Value = result(2)
End Select
End If
Next re
End Sub

How to get Google Places API (nearby search) data to Excel VBA

I have Excel sheet with a lot of addresses (and the matching coordinates). I want to use Google Places API to create a function that gets me e. g. all restaurants surrounding a specific address. I try to output the names of all matching restaurants, the issue is that the output is just "0".
Here is my code:
Function Nearby(Lat As Long, Lng As Long) As Variant
'Variablen definieren
Dim Request As New XMLHTTP30
Dim Results As New DOMDocument30
Dim StatusNode As IXMLDOMNode
Dim NearbyNode As IXMLDOMNode
On Error GoTo errorHandler
Request.Open "GET", "https://maps.googleapis.com/maps/api/place/nearbysearch/xml?location=" & Lat & "," & Lng & "&radius=50&type=restaurant&key=AIzaSyA5nFPM_9Ss_X410c35WfoP_obP5UwppRU"""
Request.send
Results.LoadXML Request.responseText
Set StatusNode = Results.SelectSingleNode("//status")
Select Case UCase(StatusNode.Text)
Case "OK"
Set NearbyNode = Results.SelectSingleNode("//result/name[1]/name[2]/name[3]/name[4]")
Nearby = NearbyNode.Text
Case "ZERO_RESULTS"
Nearby = "The address does not exists"
Case Else
Nearby = "Error"
End Select
errorHandler:
Set StatusNode = Nothing
Set NearbyNode = Nothing
Set Results = Nothing
Set Request = Nothing
End Function
I slightly reworked your code, try the below example:
Option Explicit
Sub TestNearby()
Dim NearbyNames
Dim NearbyState As String
Nearby "-33.8670522", "151.1957362", "1000", "hospital", NearbyNames, NearbyState
If NearbyState = "OK" Then
MsgBox Join(NearbyNames, vbCrLf)
Else
MsgBox NearbyState
End If
End Sub
Sub Nearby(Lat As String, Lng As String, Dist As String, PointType As String, Names As Variant, State As String)
Dim Request As Object
Dim Results As Object
Dim Node
On Error GoTo errorHandler
Set Request = CreateObject("MSXML2.XMLHTTP")
Request.Open "GET", "https://maps.googleapis.com/maps/api/place/nearbysearch/xml?" & _
"location=" & Lat & "," & Lng & _
"&radius=" & Dist & _
"&type=" & PointType & _
"&key=AIzaSyA5nFPM_9Ss_X410c35WfoP_obP5UwppRU", False
Request.send
Set Results = Request.responseXML
Select Case UCase(Results.SelectSingleNode("//status").Text)
Case "OK"
With CreateObject("Scripting.Dictionary")
For Each Node In Results.SelectNodes("//PlaceSearchResponse/result/name")
.Add .Count, Node.nodeTypedValue
Next
Names = .Items()
End With
State = "OK"
Case "ZERO_RESULTS"
State = "No results"
Case Else
State = "Wrong request"
End Select
Exit Sub
errorHandler:
State = "Error"
End Sub
The output for me is:
I changed Lat,Lng to type double and modified the Request.Open "GET" string
Sub Nearby()
Dim Request As New XMLHTTP30
Dim Results As New DOMDocument30
Dim StatusNode As IXMLDOMNode
Dim NearbyNode As IXMLDOMNode
Dim Node As IXMLDOMNode
On Error GoTo errorHandler
Dim Lat As Double 'Chicago
Lat = 41.878114
Dim Lng As Double
Lng = -87.629798
Request.Open "GET", "https://maps.googleapis.com/maps/api/place/nearbysearch/xml?location=" & Lat & "," & Lng & "&radius=50&type=restaurant&key=AIzaSyA5nFPM_9Ss_X410c35WfoP_obP5UwppRU" & "&sensor=False"
Request.send
Results.LoadXML Request.responseText
Set StatusNode = Results.SelectSingleNode("//status")
Select Case UCase(StatusNode.Text)
Case "OK"
For Each Node In Results.SelectNodes("//PlaceSearchResponse/result/name")
Debug.Print Node.nodeTypedValue
Next
Case "ZERO_RESULTS"
Debug.Print "The address does not exists"
Case Else
Debug.Print "Error"
End Select
errorHandler:
Set StatusNode = Nothing
Set NearbyNode = Nothing
Set Results = Nothing
Set Request = Nothing
End Sub
This function returns a string of comma separated values (albeit needs more error trapping) :
Public Function NearBy(Lat As Double, Lng As Double) As Variant
Dim Request As New XMLHTTP30
Dim Results As New DOMDocument30
Dim StatusNode As IXMLDOMNode
Dim NearbyNode As IXMLDOMNode
Dim Node As IXMLDOMNode
Dim sz As String
On Error GoTo errorHandler
Request.Open "GET", "https://maps.googleapis.com/maps/api/place/nearbysearch/xml?location=" & Lat & "," & Lng & "&radius=150&type=restaurant&key=AIzaSyA5nFPM_9Ss_X410c35WfoP_obP5UwppRU" & "&sensor=False"
Request.send
Results.LoadXML Request.responseText
Set StatusNode = Results.SelectSingleNode("//status")
Select Case UCase(StatusNode.Text)
Case "OK"
For Each Node In Results.SelectNodes("//PlaceSearchResponse/result/name")
sz = sz & Node.nodeTypedValue & ", "
Next
NearBy = Left(sz, Len(sz) - 2) 'remove last ", "
Case "ZERO_RESULTS"
NearBy = "The address does not exists"
Case Else
NearBy = "Error"
End Select
errorHandler:
Set StatusNode = Nothing
Set NearbyNode = Nothing
Set Results = Nothing
Set Request = Nothing
End Function

How to generate and send an email using Mozilla Thunderbird through Excel VBA

I've been looking into trying to use VBA Macro's to send an email through Mozilla Thunderbird with the spreadsheet as an attachment.
///I've searched Google and Stack Overflow itself and none of those solutions seem to be working./// I am not the best at coding or excel itself so I was just wondering if any kind soul could help me out?
Appreciate any help given.
Regards,
Looked at a load more articles and tried following what the comments have said but they didn't help. I have, however, managed to get the email portion of this to work myself. Below is the code I use
Private Declare Function ShellExecute Lib "shell32.dll" _
Alias "ShellExecuteA" (ByVal hwnd As Long, ByVal lpOperation As String, _
ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As
String, _
ByVal nShowCmd As Long) As Long
Sub Send_Email_Using_Keys()
Dim Mail_Object As String
Dim Email_Subject, Email_Send_To, Email_Cc, Email_Bcc, Email_Body As String
Email_Subject = "ACT Form Completed and Confirmed"
Email_Send_To = "kieranfarley#achievementtraining.com"
Email_Cc = "kieranfarley#achievementtraining.com"
Email_Bcc = "kieranfarley#achievementtraining.com"
Email_Body = "ACT Form Completed and Confirmed Please see attached"
Mail_Object = "mailto:" & Email_Send_To & "?subject=" & Email_Subject &
"&body=" & Email_Body & "&cc=" & Email_Cc & "&bcc=" & Email_Bcc
On Error GoTo debugs
ShellExecute 0&, vbNullString, Mail_Object, vbNullString, vbNullString,
vbNormalFocus
Application.Wait (Now + TimeValue("0:00:02"))
Application.SendKeys "%s"
debugs:
If Err.Description <> "" Then MsgBox Err.Description
End Sub
This opened the 'Write' box in thunderbird with all the fields pre-filled out ready to send.
Found some old code. Not recently tested but it worked with attachments for Thunderbird. You probably have to adapt it to your needs:
'***********************************************************************
'* Send mail with Thunderbird
'*
Option Explicit
'***********************
'* HTML formatting
'*
Private Const STARTBODY = "<html><head><style type='text/css'> body { font: 11pt Calibri, Verdana, Geneva, Arial, Helvetica, sans-serif; } </style></head><body> "
Private Const ENDBODY = "</body></htlm>"
'* Test only
Private Const ATTACHMENT1 = "C:\Temp\attachment1.pdf"
Private Const ATTACHMENT2 = "C:\Temp\attachment2.pdf"
'*******************************************************************************************
'* Test code only. Can be run by placing the cursor anywhere within the code and press F5
'* SetX THUNDERBIRD_PATH "C:\Program Files\Mozilla Thunderbird\thunderbird.exe"
'*
Private Sub MailTest()
Dim Rcp As String
Dim CC As String
Dim BCC As String
Dim Result As Boolean
Rcp = "someone#domain.com"
CC = "someoneelse#domain.com"
BCC = "onedude#domain.com"
Result = SendMail(Rcp, CC, BCC, "Test", "Hello World", False, ATTACHMENT1 & ";" & ATTACHMENT2)
End Sub
'****************************************************************************
'* Send e-mail through Thunderbird
'* SetX THUNDERBIRD_PATH "C:\Program Files\Mozilla Thunderbird\thunderbird.exe"
'*
Function SendMail(strTo As String, _
strCC As String, _
strBCC As String, _
strSubject As String, _
strMessageBody As String, _
Optional PlainTextFormat As Boolean = False, _
Optional strAttachments As String = "", _
Optional SignatureFile As String = "") As Boolean
Dim Cmd As String
Dim Arg As String
Dim Result As Integer
Dim objOutlook As Outlook.Application
Dim MAPISession As Outlook.NameSpace
Dim MAPIMailItem As Outlook.MailItem
Dim strTemp As String
Dim MailResult As Boolean
Dim I As Integer
Dim Account As Object
MailResult = False
Cmd = Environ("THUNDERBIRD_PATH") 'E:\Program Files\Mozilla Thunderbird\thunderbird.exe
If Cmd <> "" Then ' Thunderbird installed
Arg = " -compose """
strTo = Replace(strTo, ";", ",")
If strTo <> "" Then Arg = Arg & "to='" & strTo & "',"
strCC = Replace(strCC, ";", ",")
If strCC <> "" Then Arg = Arg & "cc='" & strCC & "',"
strBCC = Replace(strBCC, ";", ",")
If strBCC <> "" Then Arg = Arg & "bcc='" & strBCC & "',"
If strSubject <> "" Then Arg = Arg & "subject=" & strSubject & ","
If PlainTextFormat = True Then
strTemp = "2" 'Plain text
Else
strTemp = "1" 'HTML
strMessageBody = STARTBODY & strMessageBody & ENDBODY 'Add HTML and CSS
End If
Arg = Arg & "format=" & strTemp & "," 'Format specifier HTML or Plain Text
Arg = Arg & "body='" & strMessageBody & "'," 'Add body text
Call AddSignature(SignatureFile, strMessageBody) 'Add signature if any
Arg = Arg & "attachment='"
Call AddAttachments(strAttachments, , Arg) 'Add attachment(s) if any
Arg = Arg & "'""" 'Closing quotes
Shell Cmd & Arg 'Call Thunderbird to send the message
MailResult = True
SendMail = MailResult
End Function
'*******************************************************************
'* Add recipients, CC or BCC recipients to the email message
'* Recipients is a string with one or more email addresses,
'* each separated with a semicolon
'* Returns number of addresses added
'*
Private Function AddRecipients(Recipients As String, MAPIMailItem As Outlook.MailItem, RecType As Integer) As Integer
Dim OLRecipient As Outlook.Recipient
Dim TempArray() As String
Dim Recipient As Variant
Dim Emailaddr As String
Dim Count As Integer
Count = 0
TempArray = Split(Recipients, ";")
For Each Recipient In TempArray
Emailaddr = Trim(Recipient)
If Emailaddr <> "" Then
Set OLRecipient = MAPIMailItem.Recipients.Add(Emailaddr)
OLRecipient.Type = RecType
Set OLRecipient = Nothing
Count = Count + 1
End If
Next Recipient
AddRecipients = Count
End Function
'******************************************************
'* Add possible signature to the email message
'* Returns True if signature added
'*
Private Function AddSignature(SignatureFile As String, ByRef strMessageBody As String) As Boolean
Dim Signature As String
Dim Tempstr As String
Dim Added As Boolean
Added = False
If SignatureFile <> "" Then
Signature = ""
Open SignatureFile For Input As #1 'Open file for reading
Do While Not EOF(1) 'Loop through file
Input #1, Tempstr 'One line
Signature = Signature & Tempstr 'Add it
Loop
Close #1
strMessageBody = strMessageBody & Signature 'Add to message
Added = True
End If
AddSignature = Added
End Function
'******************************************************
'* Add possible attachments to the email message
'* Returns number of attachments added
'*
Private Function AddAttachments(ByRef strAttachments As String) As Integer
Dim TempArray() As String
Dim Attachment As Variant
Dim Tempstr As String
Dim Count As Integer
Count = 0
TempArray = Split(strAttachments, ";")
For Each Attachment In TempArray
Tempstr = CStr(Trim(Attachment))
If Tempstr <> "" Then
If Count > 0 Then Arg = Arg & ","
Arg = Arg & "file:///" & Tempstr
End If
Count = Count + 1
Next Attachment
AddAttachments = Count
End Function
The code below iterates through a range in excel and for each record marked for sending it will send an email using Thunderbird. Additionally, if the path to a file is specified it will attach that file. Be careful with the apostrophes when building the command string. If you get them wrong the non-printing characters will be removed from the message body for some reason.
Public Sub sendEmail(subject As String, msg As String, path As String)
Dim contactRange As Range, cell As Range
Dim count As Integer
Dim thund As String
Dim email As String
Dim recipientName As String
Dim pathToThunderBird
Set contactRange = Range("ContactYesNo")
pathToThunderBird = "C:\Program Files (x86)\Mozilla Thunderbird\thunderbird.exe "
With Worksheets("IT consulting")
For Each cell In contactRange
If cell.Value = "Yes" Then
count = count + 1
recipientName = cell.Offset(0, 2).Value
email = cell.Offset(0, 6).Value
emailMsg = "Hi " & recipientName & vbCrLf & vbCrLf & msg & vbCrLf
'You'll want to change the salutation.
thund = pathToThunderBird & _
"-compose " & """" & _
"to='" & email & "'," & _
",subject='" & subject & "'," & _
",body='" & emailMsg & vbCrLf & vbCrLf & _
"Your Name" & vbCrLf & _
"123.456.7890" & "'" & """"
If path = "" Then 'no attachment
'do nothing
Else 'with attachment
thund = thund & ",attachment=" & path
End If
Call Shell(thund, vbNormalFocus)
'comment this out if you do not want to send automatically
SendKeys "^+{ENTER}", True
End If
Next cell
End With
End Sub

VBA Macro to download multiple files from links in IE

I want to download multiple files from a list of links. The website where I find the links is protected. This is why I want to use IE (using the current session/cookie). The target of each link is a xml file. The files are too large to open and then save. So I need to save them directly (right-click, save target as).
The list of links looks like this:
<html>
<body>
<p> <a href="https://example.com/report?_hhhh=XML"Link A</a><br>> </p>
<p> <a href="https://example.com/report?_aaaa=XML"Link B</a><br>> </p>
...
</body>
</html>
I want to loop through all links and save each target. Currently I have problems with the "Save As". I don't really know how to do it. This is my code so far:
Sub DownloadAllLinks()
Dim IE As Object
Dim Document As Object
Dim List As Object
Dim Link As Object
' Before I logged in to the website
Set IE = CreateObject("InternetExplorer.Application")
IE.Visible = True
IE.Navigate ("https:\\......\links.html")
Do While IE.Busy
DoEvents
Loop
' Detect all links on website
Set Document = IE.Document
Set List = Document.getElementsByTagName("a")
' Loop through all links to download them
For Each Link In List
' Now I need to automate "save target as" / right-click and then "save as"
...
Next Link
End Sub
Do you have any ideas to automate "Save As" for each link?
Any help is appreciated. Many thanks,
Uli
Private Declare PtrSafe Function Test Lib "urlmon" _
Alias "URLDownloadToFileA" (ByVal pCaller As Long, _
ByVal szURL As String, ByVal szFileName As String, _
ByVal dwReserved As Long, ByVal lpfnCB As Long) As Long
Sub AutoOpen()
Dim strFile As String
Dim strURL As String
Dim strPath As String
Dim ret As Long
Dim strFile1 As String
Dim strURL1 As String
Dim strPath1 As String
Dim ret1 As Long
Dim Shex As Object
Dim Test2 As String
strFile = "1st_file"
strURL = "first-url" & strFile
strPath = Environ("UserProfile") & "your-path" & strFile
ret = Test(0, strURL, strPath, 0, 0)
strFile1 = "something_else"
strURL1 = "your-url" & strFile1
strPath1 = Environ("UserProfile") & "your-path" & strFile1
re1t = Test(0, strURL1, strPath1, 0, 0)
If ret <> 0 Then MsgBox "Something went wrong!", vbInformation
End Sub
You can use this macro to download multiple files. To download even more just duplicate this part
Dim strFile As String
Dim strURL As String
Dim strPath As String
Dim ret As Long
and this part:
strFile = "1st_file"
strURL = "first-url" & strFile
strPath = Environ("UserProfile") & "your-path" & strFile
ret = Test(0, strURL, strPath, 0, 0)
Obviously just change the variables and then you are good to go.
Below is a quite common example I adapted for your case, it shows the usage of XHR and RegEx to retrieve webpage HTML content, extract all links from it, and download each link's target file:
Option Explicit
Sub Test()
' declare vars
Dim sUrl As String
Dim sReqProt As String
Dim sReqAddr As String
Dim sReqPath As String
Dim sContent As String
Dim oLinks As Object
Dim oMatch As Object
Dim sHref As String
Dim sHrefProt As String
Dim sHrefAddr As String
Dim sHrefPath As String
Dim sHrefFull As String
Dim n As Long
Dim aContent() As Byte
' set source URL
sUrl = "https:\\......\links.html"
' process source URL
SplitUrl sUrl, sReqProt, sReqAddr, sReqPath
If sReqProt = "" Then sReqProt = "http:"
sUrl = sReqProt & "//" & sReqAddr & "/" & sReqPath
' retrieve source page HTML content
With CreateObject("Microsoft.XMLHTTP")
.Open "GET", sUrl, False
.Send
sContent = .ResponseText
End With
' parse source page HTML content to extract all links
Set oLinks = CreateObject("Scripting.Dictionary")
With CreateObject("VBScript.RegExp")
.Global = True
.MultiLine = True
.IgnoreCase = True
.Pattern = "<a.*?href *= *(?:'|"")(.*?)(?:'|"").*?>"
For Each oMatch In .Execute(sContent)
sHref = oMatch.subMatches(0)
SplitUrl sHref, sHrefProt, sHrefAddr, sHrefPath
If sHrefProt = "" Then sHrefProt = sReqProt
If sHrefAddr = "" Then sHrefAddr = sReqAddr
sHrefFull = sHrefProt & "//" & sHrefAddr & "/" & sHrefPath
oLinks(oLinks.Count) = sHrefFull
Next
End With
' save each link target into file
For Each n In oLinks
sHref = oLinks(n)
With CreateObject("Microsoft.XMLHTTP")
.Open "GET", sHref, False
.Send
aContent = .ResponseBody
End With
With CreateObject("ADODB.Stream")
.Type = 1 ' adTypeBinary
.Open
.Write aContent
.SaveToFile "C:\Test\" & n & ".xml", 2 ' adSaveCreateOverWrite
.Close
End With
Next
End Sub
Sub SplitUrl(sUrl, sProt, sAddr, sPath)
' extract protocol, address and path from URL
Dim aSplit
aSplit = Split(sUrl, "//")
If UBound(aSplit) = 0 Then
sProt = ""
sAddr = sUrl
Else
sProt = aSplit(0)
sAddr = aSplit(1)
End If
aSplit = Split(sAddr, "/")
If UBound(aSplit) = 0 Then
sPath = sAddr
sAddr = ""
Else
sPath = Mid(sAddr, Len(aSplit(0)) + 2)
sAddr = aSplit(0)
End If
End Sub
This method doesn't employ IE automation. Usually the IE's cookies which Microsoft.XMLHTTP processes are sufficient to refer to the current session, so if your website doesn't use additional procedures for authentication and generation the list of the links then the method should work for you.