VBA URLDownloadToFileA or URLOpenStreamA from URLMON - Asynchronously - excel

I'm using URLDownloadToFileA API call to download files in VBA, which works fine (and is MUCH faster than WinHTTP or XMLHTTP), but it's synchronous only.
I've been searching for ways to use a URLMon API call asynchronously (perhaps using URLOpenStream instead of download-to-file), but haven't figured out a way to do this.
I stumbled upon VB6 code that might be able to do it here: http://www.mvps.org/emorcillo/download/vb6/adl.zip but I am not versed enough in coding to convert this to working VBA.
Please note: I do realize how to do this through XMLHTTP and WinHTTP with a class, but those are significantly slower than using the URLMon DLL API, so am hoping to find a solution there.
Code to do it synchronously with URLMon:
Private Declare PtrSafe Function URLDownloadToFileA Lib "URLMON" (ByVal pcaller As Long, ByVal szurl As String, ByVal szFileName As String, ByVal dwReserved As Long, ByVal lpfnCB As Long) As LongPtr
Private Declare PtrSafe Function URLOpenPullStreamA Lib "URLMON" (ByVal pcaller As Long, ByVal szurl As String, ByVal dwReserved As Long, ByVal lpfnCB As Long) As LongPtr
Sub test()
dim a&, b&, URL$
URL = "http://ipv4.download.thinkbroadband.com/100MB.zip"
a = URLOpenPullStreamA (0, URL, 0, 0)
b = URLDownloadToFileA(0, URL, "c:\testfiles\100MB.zip", 0, 0)
End Sub
So, this works - but I don't know how to capture the callback for a, and while downloading b it locks excel until the file is fully downloaded.
Any help would be greatly appreciated!

One way would be to start the VBA code in a separate Office application, have it exit upon the end of the synchronous download and monitor when it exits from the main application.

Related

Download Excel file from Sharepoint, Save to Desktop

I am trying to download an excel file stored in sharepoint (no unc path available) to my desktop.
This code below seems to work and create "CST.xlsx" but I get an error msg:
Notes:
Url for excel file is taken directly from the web address bar and everthing after ".xlsx" is removed.
Code:
Private Declare Function URLDownloadToFile 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
Function DownloadFileFromWeb(strURL As String, strSavePath As String) As Long
' strSavePath includes filename
DownloadFileFromWeb = URLDownloadToFile(0, strURL, strSavePath, 0, 0)
End Function
Sub download()
Call DownloadFileFromWeb("url.xlsx", "Desktop\download\CST.xlsx")
End Sub
See my answer in this thread about the same problem for a workaround.

Save webpage concluded instead HTML only

I'm currently using a VBA code in a spreadsheet to download a webpage, the issue is that this is downloading only the html page, without the folder that is downloaded when we go to the browser and select "Save As".
Below I'm showing the explanation with images - I would like to know if is possible to, directly from VBA, download the concluded page (with folder that comes with images, etc.) instead only the HTML.
Private Declare PtrSafe Function URLDownloadToFile 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 datafeed()
src = "https://google.com"
dlpath = "C:\Users\User\Downloads\Test\"
URLDownloadToFile 0, src, dlpath & "test.html", 0, 0
End Sub
How is currently downloading:
How I would like to download (like when I select Save As in the browser):
Does someone know how to configure VBA to download the webpage and the folder with the files instead only the webpage?

Wininet.dll crashes excel 64 bit when extracting cookies

my company moved from 32 bit excel to 64 and and now macros that used to pull cookies keeps crashing. I know about PtrSafe declaration, but this no longer works. K googled trying to find the correct declaration for it but can't seem to get it right. Maybe can someone point out where the LongLong or LongPtr needs to be used? Below code is my n-th try with no luck:
'clear current cookies
Private Declare PtrSafe Function InternetSetOption Lib "wininet.dll" Alias "InternetSetOptionA" (ByVal hInternet As Long, ByVal lOption As Long, ByVal sBuffer As String, ByVal lBufferLength As Long) As LongPtr
'retrieve cookie
Private Declare PtrSafe Function InternetGetCookieEx Lib "wininet.dll" Alias "InternetGetCookieExA" (ByVal pchURL As String, ByVal pchCookieName As String, ByVal pchCookieData As String, ByRef pcchCookieData As Integer, ByVal dwFlags As Integer, ByVal lpReserved As Integer) As Boolean
Private Const INTERNET_OPTION_END_BROWSER_SESSION = 42
Private Const INTERNET_COOKIE_HTTPONLY As Integer = &H2000
Private Sub UserForm_Initialize()
Call InternetSetOption(0, INTERNET_OPTION_END_BROWSER_SESSION, 0, 0)
WebBrowser1.Silent = True
'WebBrowser1.Navigate "salesforce.com"
WebBrowser1.Navigate "salesforce.com"
End Sub
Private Sub WebBrowser1_DocumentComplete(ByVal pDisp As Object, URL As Variant)
If URL Like "*/home.jsp*" Then
Call InternetGetCookieEx(URL, "sid", sessionId, 256, INTERNET_COOKIE_HTTPONLY, vbNull)
Unload Me
End If
End Sub
The answer is to check the Microsoft documentation for these commands. When you are calling native methods like this you will find the documentation is in C++ so we must do some translation.
We can also look at what Microsoft says about LongPtr.
LongPtr is not a true data type because it transforms to a Long in
32-bit environments, or a LongLong in 64-bit environments. Using
LongPtr enables writing portable code that can run in both 32-bit and
64-bit environments. Use LongPtr for pointers and handles.
In your case that will be at least the handle hInternet but I would also cover the buffer length to avoid overflow problems. I would also convert all Integers to Longs.
Private Declare PtrSafe Function InternetSetOption Lib "wininet.dll" Alias "InternetSetOptionA" (ByVal hInternet As LongPtr, ByVal lOption As Long, ByVal sBuffer As String, ByVal lBufferLength As LongPtr) As LongPtr
Private Declare PtrSafe Function InternetGetCookieEx Lib "wininet.dll" Alias "InternetGetCookieExA" (ByVal pchURL As String, ByVal pchCookieName As String, ByVal pchCookieData As String, ByRef pcchCookieData As Long, ByVal dwFlags As Long, ByVal lpReserved As LongPtr) As Boolean
EDIT: Let's also declare lpReserved As LongPtr because it starts with lp, which could mean Long Pointer.
ERRORS YOU MUST GUARD FROM:
To avoid unhandled exceptions in native code, check the exceptions that this command can throw and guard against them before calling in to native methods.
Return code Description
ERROR_NO_MORE_ITEMS
There is no cookie for the specified URL and all its parents.
ERROR_INSUFFICIENT_BUFFER
The value passed in lpdwSize is insufficient to copy all the cookie
data. The value returned in lpdwSize is the size of the buffer
necessary to get all the data.
ERROR_INVALID_PARAMETER
One or more of the parameters is invalid.
The lpszUrl parameter is NULL.

Troubled with VBA version difference: version 6 and version 7

I'm new to VBA and use excel 2010 64bit VBA v6.0 compatible. I pasted the code, trying to download files through VBA.
Option Explicit
'Tutorial link: https://youtu.be/H4-w6ULc_qs
#If VBA7 Then
Private Declare Function URLDownloadToFile Lib "urlmon" Alias _
"URLDownloadToFileA" (ByVal pCaller As LongPtr, ByVal szURL As String, ByVal _
szFileName As String, ByVal dwReserved As LongPtr, ByVal lpfnCB As LongPtr) As LongPtr
#Else
Private Declare Function URLDownloadToFile 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
#End If
Sub download_file()
'-----------------------------
'Thanks for downloading the code.
'Please visit our channel for a quick explainer on how to use this code.
'Feel free to update the code as per your need and also share with your friends.
'Download free codes from http://vbaa2z.blogspot.com
'Support our channel: youtube.com/vbaa2z
'Author: L Pamai (vbaa2z.team#gmail.com)
'-----------------------------
Dim downloadStatus As Variant
Dim url As String
Dim destinationFile_local As String
url = [D3]
destinationFile_local = "C:\Users\myUserName\Downloads\" & fileName([D3])
downloadStatus = URLDownloadToFile(0, url, destinationFile_local, 0, 0)
If downloadStatus = 0 Then
MsgBox "Downloaded Succcessfully!"
Else
MsgBox "Download failed"
End If
End Sub
Function fileName(file_fullname) As String
fileName = Mid(file_fullname, InStrRev(file_fullname, "/") + 1)
End Function
However, a pop-up window says it can only run on 64-bit systems as follow:
Compile error:
The code in this project must be updated for use on 64-bit systems. Please review and update Declare statements and then mark them with the PtrSafe attribute.
My questions are:
I do use window and office 64-bit system. Why the window keeps popping up?
Is there any way to solve this problem?
Thanks in advance.
As the error tells you, add the PtrSafe keyword to the VBA7 branch
#If VBA7 Then
Private Declare PtrSafe Function URLDownloadToFile Lib "urlmon" Alias _
"URLDownloadToFileA" (ByVal pCaller As LongPtr, ByVal szURL As String, ByVal _
szFileName As String, ByVal dwReserved As Long, ByVal lpfnCB As LongPtr) As Long
#Else
Private Declare Function URLDownloadToFile 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
#End If
You need to add this keyword anywhere you are using LongPtr, or LongLong.
Here is the MS Documentation on PtrSafe
https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/ptrsafe-keyword
Adding the PtrSafe keyword to a Declare statement only signifies that the Declare statement explicitly targets 64-bits. All data types within the statement that need to store 64-bits (including return values and parameters) must still be modified to hold 64-bit quantities by using either LongLong for 64-bit integrals or LongPtr for pointers and handles.

VBA - close non-excel file

I have a question I am unable to find the answer to. I have a macro that imports data from a .MHTML file into my worksheet (Using the .MHTML file is my only option unfortunately. It is an export from SAP and opens in Excel but is not recognized as an Excel file due to file extension type.). At the end of the macro I wouuld like to close it. It is not recognized as an excel workbook so I am unable to use the simple: Workbooks().close command.
Does anyone have any ideas on how to do this? Thanks in advance.
I was able to find this code and after changing the Caption to match what is shown in Task Manager, it worked: http://www.vbforums.com/showthread.php?208430-Use-sendmessage-to-close-an-application
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Const WM_CLOSE = &H10
Sub ExportClose()
Dim CloseIt As Long
CloseIt = FindWindow(vbNullString, "Microsoft Excel - SAP_export.MHTML")
PostMessage CloseIt, WM_CLOSE, CLng(0), CLng(0)
End Sub
Note: This works great for my system but not for my colleague
.. Currently trying to figure out why
Maybe try just:
Windows("SAP_export.MHTML").Close

Resources