Closed longpants closed 8 years ago
For IE, you'll have to edit this registry key manually: HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\Default Download Directory
Or with some code:
Const reg_key = "HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\Default Download Directory"
Const reg_val = "C:\Downloads"
CreateObject("WScript.Shell").RegWrite reg_key, reg_val, "REG_SZ"
hmm bummer :-)
But thanks!
Sorry to ask, but how can I trigger this download from IE? IE shows a popup. Can I work around this?
I was struggling with the same problem and there are some hints around to control the IE download dialog via internal object handles. This was not my option of choice due to the lack of browser independence and having in mind potential migration effort to Edge IMHO this is today even less an option of choice.
I decided a kind of "hybrid" approach: all data downloaded is requested from the respective server using a HTTP request. Instead of the remotely controlled browser sending the request, the application must be enabled to send this request directly and you are done. You just need the parameters ... but these ar enot to difficult to find out with some HTTP request sniffing using Fiddler or FireFox addins. To circumvene the IE dialog box I just simulate a natural user via IE remote control until I can retrieve all necessary information (like session IDs, authorization IDs etc.) to build the appropriate HTTP request and send it directly to the webserver. Advantage: this approach is independent of the webdriver.
It looks like the automatic downloading has been removed from IE11. The automatic opening is still there so it is still possible by customizing the registry:
Private Sub Usage_Download_Link_IE()
Dim driver As New IEDriver, ele As WebElement
driver.Get "https://www.mozilla.org/en-US/foundation/documents"
Set ele = driver.FindElementByLinkText("IRS Form 872-C")
Download_Link_IE ele, "C:\Downloads\irs-form-872-c_2.pdf"
driver.Quit
End Sub
Private Sub Download_Link_IE(ele As WebElement, save_as As String)
' Activate the automatic opening for the extension and set xcopy as default application
Static reg As Object, shl As Object, progid$, extension$
If shl Is Nothing Then
Set shl = CreateObject("WScript.Shell")
shl.RegWrite "HKCU\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_RESTRICT_FILEDOWNLOAD\iexplore.exe", 0, "REG_DWORD"
End If
extension = Mid$(save_as, InStrRev(save_as, "."))
progid = shl.RegRead("HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\" & extension & "\UserChoice\ProgId")
Shell "reg ADD HKCU\Software\Microsoft\Windows\Shell\AttachmentExecute\{0002DF01-0000-0000-C000-000000000046} /v " & progid & " /t REG_BINARY", vbHide
shl.RegWrite "HKCU\Software\Classes\" & progid & "\shell\", "download", "REG_SZ"
shl.RegWrite "HKCU\Software\Classes\" & progid & "\shell\download\command\", "xcopy /Y ""%1"" """ & save_as & "*""", "REG_SZ"
' Click the file to download
If Len(Dir$(save_as)) Then Kill save_as
ele.Click
' Wait fo the downloading to finish
Dim Waiter As New Selenium.Waiter
Waiter.WaitForFile save_as, 10000
' Restore the default application
shl.RegWrite "HKCU\Software\Classes\" & progid & "\shell\", "open", "REG_SZ"
End Sub
You could also download the file with a simple http request. However this solution only works if the href attribute has a direct link:
Private Sub Usage_Download_Link_IE_2()
Dim driver As New IEDriver, ele As WebElement
driver.Get "https://www.mozilla.org/en-US/foundation/documents"
Set ele = driver.FindElementByLinkText("IRS Form 872-C")
Download_LinkHref ele, "C:\Downloads\irs-form-872-c_1.pdf"
driver.Quit
End Sub
Private Sub Download_LinkHref(ele As WebElement, save_as As String)
' Extract the data to build the request (link, user-agent, language, cookie)
Dim info As Selenium.Dictionary
Set info = ele.ExecuteScript("return {" & _
"'link': this.href," & _
"'agent': navigator.userAgent," & _
"'lang': navigator.userLanguage," & _
"'cookie': document.cookie };")
' Send the request
Static xhr As Object
If xhr Is Nothing Then Set xhr = CreateObject("Msxml2.ServerXMLHTTP.6.0")
xhr.Open "GET", info("link")
xhr.setRequestHeader "User-Agent", info("agent")
xhr.setRequestHeader "Accept-Language", info("lang")
xhr.setRequestHeader "Cookie", info("cookie")
xhr.Send
If (xhr.Status \ 100) - 2 Then Err.Raise 5, , xhr.Status & " " & xhr.StatusText
' Save the response to a file
Static bin As Object
If bin Is Nothing Then Set bin = CreateObject("ADODB.Stream")
If Len(Dir$(save_as)) Then Kill save_as
bin.Open
bin.Type = 1
bin.Write xhr.ResponseBody
bin.Position = 0
bin.SaveToFile save_as
bin.Close
End Sub
Hi Florent, Thanks for your input (again)!
Unfortunately the timer times-out, and no file pops. (And the a href
doesnt have a direct URL)
After spending almost whole day googling for solution I came up to this solution: (modified and made suitable for my project)
Function fnSaveIEDownload(v As Variant) As Boolean
Dim i As Integer
Dim h As Long
'Make sure UIAutomationClient is enabled in References
'C:\Windows\System32\UIAutomationCore.dll
i = -1
restart:
i = i + 1
On Error GoTo errhandler:
Dim o As IUIAutomation
Dim e As IUIAutomationElement
Set o = New CUIAutomation
'SetForegroundWindow h
h = FindWindowEx(testobjectWindowTitle, 0, "Frame Notification Bar", vbNullString)
If h = 0 Then GoTo errhandler
Set e = o.ElementFromHandle(ByVal h)
Dim iCnd As IUIAutomationCondition
Set iCnd = o.CreatePropertyCondition(UIA_NamePropertyId, v(i))
Dim Button As IUIAutomationElement
Set Button = e.FindFirst(TreeScope_Subtree, iCnd)
Dim InvokePattern As IUIAutomationInvokePattern
Set InvokePattern = Button.GetCurrentPattern(UIA_InvokePatternId)
InvokePattern.Invoke
If Not Button Is Nothing Then fnSaveIEDownload = True
Exit Function
errhandler:
If i < UBound(v) Then GoTo restart
fnSaveIEDownload = False
MsgBox "Check VBA reference: 'UIAutomationClient' is installed." & Chr(10) _
& Chr(10) & "Default path: 'C:\Windows\System32\UIAutomationCore.dll'" & Chr(10) & _
Chr(10) & "Note that this auto download only works from IE10, else: manual action required" _
, vbCritical, "Error loading VBA component"
End Function
Perform your click action on the Webelement and fnSaveIEDownload(array("Opslaan","Save")
will do the rest. I use the e.Executescript ("this.click();") since this doesn't freeze the VBA code.
In which v
can be "Save" or "Opslaan", depending on local language...
Pro:
Con:
I hope this helps other people as well.
Interesting idea longpants. Thanks for pasting the code. I'm stuck using IE11 and trying to get it download selected files. I can't alter my registry and the file don't have their specific URL for download so I can't do the HTTP request from Florent
Have you or any tried just using the sendkeys to navigate to the download dialog? I've been trying but with no luck. (I know sendkeys is not a great alternative for automation) I can't seem to to get the shift+tab to move to the dialog box and then move through the options to open save as. If you the send keys for CTRL+J it appears as directed, but I can't seem to get it to focus on that new popup.
Any ideas on if its possible to set the focus for CTRL+J?
driver.FindElementByClass("MyDownloadButton").Click
driver.Wait 3000
'driver.SendKeys (Keys.Shift & Keys.Tab)
driver.SendKeys Keys.Control, "j" 'Open the view downloads box
driver.SendKeys Keys.ArrowRight 'Navigate to the Save As
driver.SendKeys Keys.ArrowRight 'Navigate to the Save As
driver.SendKeys Keys.ArrowDown 'Navigate to the Save As
driver.SendKeys Keys.ArrowDown 'Navigate to the Save As
driver.SendKeys Keys.Enter 'Enter the Save As Dialog
driver.Wait 200
driver.SendKeys " & MyStringLabel & " & "_" & "ALL" & ".zip" 'Setting the file name and type
driver.Wait 200
driver.SendKeys Keys.Tab 'Navigating to the file path input
driver.SendKeys Keys.Tab 'Navigating to the file path input
driver.SendKeys Keys.Tab 'Navigating to the file path input
driver.SendKeys Keys.Tab 'Navigating to the file path input
driver.SendKeys Keys.Tab 'Navigating to the file path input
driver.SendKeys " & Path & " 'Paste in the string path for saving to the folder
driver.Wait 200
driver.SendKeys Keys.Enter 'Save the file(s)
Thanks, not sure, but I think sending keys by the Driver sends the keys to the HTML, not the browser or IE window. But I can be wrong. You can change that to Application.SendKeys
. Check Google or i.e.: http://www.contextures.com/excelvbasendkeys.html for options.
With my solution there is no need to change windows register, only check the VBA References to make the automation DLL enabled.
I used some other feature to change the default download folder for IE. Every time I run SeleniumBasic I start and end with running this script. Based on the RunCleaner.vbs
by Florent:
The code below is called by: fnKillWebDriverSessions
Function fnKillWebDriverSessions()
On Error GoTo errFunction
If Not testObject Is Nothing Then
testObject.Quit
Set testObject = Nothing '.Quit
End If
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
Dim waitOnReturn As Boolean: waitOnReturn = True
'Dim windowStyle As Integer: windowStyle = 1
wsh.run "wscript " & Range("rSelenium").value & "\Scripts\RunCleanerSilent.vbs", vbHide, waitOnReturn
'wsh.run "C:\folder\runbat.bat", windowStyle, waitOnReturn
' Shell "wscript " & Range("rSelenium").value & "\Scripts\RunCleanerSilent.vbs", vbHide ' vbNormalFocus
Application.Wait 2000
Exit Function
errFunction:
If blDebug Then
Stop
Resume
End If
End Function
And the RunCleanerSilent.vbs
script contains:
' Utility script to
' Call driver.Quit on each active session
' Terminate all the background drivers (chromedriver, iedriver, operadriver, phantomjs)
' Delete the temporary folder (%TEMP%\Selenium)
Sub Main()
Call QuitSessions
Call TerminateDrivers
Call DeleteTemporaryFolder
Call ResetIEDownloadFolder
'Wscript.Echo "Done!"
End Sub
'Quits all the registered sessions
Sub QuitSessions()
Err.Clear
On Error Resume Next
Do
GetObject("Selenium.WebDriver").Quit
Loop Until Err.Number
End Sub
'Terminates all the drivers and all the child processes
Sub TerminateDrivers()
names = Array("chromedriver.exe", "iedriver.exe", "operadriver.exe", "phantomjs.exe", "edgedriver.exe")
Set mgt = GetObject("winmgmts:")
On Error Resume Next
For Each p In mgt.ExecQuery("Select * from Win32_Process Where Name='" & Join(names, "' Or Name='") & "'")
For Each cp In mgt.ExecQuery("Select * from Win32_Process Where ParentProcessId=" & p.ProcessId)
cp.Terminate
Next
p.Terminate
Next
End Sub
'Deletes all the files and folders in "%TEMP%\Selenium"
Sub DeleteTemporaryFolder()
Set sho = CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")
folder = sho.ExpandEnvironmentStrings("%TEMP%\Selenium")
If fso.FolderExists(folder) Then
Set folderObj = fso.GetFolder(folder)
On Error Resume Next
For Each subfolderObj in folderObj.SubFolders
subfolderObj.Delete True
Next
For Each fileObj in folderObj.Files
fileObj.Delete True
Next
folderObj.Delete True
End If
End Sub
'call main
'exit
Sub ResetIEDownloadFolder()
Const reg_key_download_backup = "HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\Default Download Directory Backup XLS Webdriver"
Const reg_key_download_present = "HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\Default Download Directory Present XLS Webdriver"
Const reg_key_download_default = "HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\Default Download Directory"
Dim reg_val 'As String
'On Error GoTo ExitSub
'check default key present, check if empty then delete
If isKeyPresent(reg_key_download_default) Then
If CreateObject("WScript.Shell").RegRead(reg_key_download_default) = "" Then CreateObject("WScript.Shell").RegDelete reg_key_download_default
End If
If isKeyPresent(reg_key_download_default) Then
If isKeyPresent(reg_key_download_backup) Then
'if backup is present, there was running instance, so reset (and delete backup)
regv_val = CreateObject("WScript.Shell").RegRead(reg_key_download_backup)
CreateObject("WScript.Shell").RegWrite reg_key_download_default, regv_val, "REG_SZ"
CreateObject("WScript.Shell").RegDelete reg_key_download_backup
'if default was not present initially, remove as well
If CreateObject("WScript.Shell").RegRead(reg_key_download_present) = 0 Then CreateObject("WScript.Shell").RegDelete reg_key_download_default
Else
'backup not present (so new start), store default in backup and set Present was true (1)
regv_val = CreateObject("WScript.Shell").RegRead(reg_key_download_default)
CreateObject("WScript.Shell").RegWrite reg_key_download_backup, regv_val, "REG_SZ"
CreateObject("WScript.Shell").RegWrite reg_key_download_present, 1, "REG_SZ"
End If
Else
'default key not present, store this information
CreateObject("WScript.Shell").RegWrite reg_key_download_present, 0, "REG_SZ"
CreateObject("WScript.Shell").RegWrite reg_key_download_backup, 0, "REG_SZ"
End If
'ExitSub:
End Sub
Function isKeyPresent(sKey) 'As Boolean
On Error Resume Next
isKeyPresent = False
Dim myKey
Dim keyValue
Set myKey = CreateObject("WScript.Shell")
keyValue = myKey.RegRead(sKey)
If keyValue = "" Then
isKeyPresent = False
Else
isKeyPresent = True
End If
End Function
Call Main
Logic: Check whether default IE path is present, save this status and backup the path. Then during runtime the default path gets updated to a specific folder with timestamp. And on closure or when starting after abnormal quit of code the execution of the script checks whether default folder was present and removes all otherwise. I'm working on 'protected' laptop as well by the company, but I can change my register on local user key. You can give it a try?
Thanks for the reply Longpants. Let me take some time to digest it. I understand the logic and the code behind it, I will try to come up with a similar idea for my project. I need to do some reading and learn more about the UIAutomationCore reference.
And yes, I was being dumb and forgot the driver is controlling the HTML. Once I set it to the application sendkeys, I was able to get it to put in my desired path and filename in the save as dialog. Just obviously, I wan to avoid using send keys for something like this; especially in automation environment.
No thanks, I received a lot of help here (especially from Florent), happy to help you. Indeed, I'm trying to avoid the send keys as well, it's not very robust. I'm neither familiair with UIAutomationCore, but happy that it runs smoothly :-)
For the record, it's also possible to press the Save button by sending keys (shortcut Alt+S). It should be possible with driver.SendKeys Keys.Alt, "s", but unfortunately the underlying IE driver doesn't send properly the Alt key. Here is an example that waits natively for the download dialogue and presses Save without the need to focus on the window:
Private Declare PtrSafe Function FindWindowExA Lib "user32.dll" ( _
ByVal hwndParent As LongPtr, _
ByVal hwndChildAfter As LongPtr, _
ByVal lpszClass As String, _
ByVal lpszWindow As String) As Long
Private Declare PtrSafe Function PostMessageA Lib "user32.dll" ( _
ByVal Hwnd As LongPtr, _
ByVal wMsg As LongPtr, _
ByVal wParam As LongPtr, _
ByVal lParama As LongPtr) As Long
Private Sub ConfirmSaveKeyboard()
Const timeout = 5000
' wait for the download dialogue (IEFrame/Frame Notification Bar/DirectUIHWND)
Dim ie_hwnd, frm_hwnd, dlg_hwnd, endtime#, i&
ie_hwnd = FindWindowExA(0, 0, "IEFrame", vbNullString)
endtime = Now + timeout / 86400#
Do
frm_hwnd = FindWindowExA(ie_hwnd, 0, "Frame Notification Bar", vbNullString)
If frm_hwnd Then
dlg_hwnd = FindWindowExA(frm_hwnd, 0, "DirectUIHWND", vbNullString)
If dlg_hwnd Then Exit Do
End If
If Now > endtime Then Err.Raise 5, , "Failed to find the download dialogue"
For i = 1 To 10000: DoEvents: Next
Loop
' press Alt + S (Shortcut assigned to the Save button)
PostMessageA ie_hwnd, &H104&, &H12, &H20000001 'WM_SYSKEYDOWN, VK_MENU
PostMessageA ie_hwnd, &H104&, &H53, &H20000001 'WM_SYSKEYDOWN, S
PostMessageA ie_hwnd, &H101&, &H53, &HC0000001 'WM_KEYUP, S
PostMessageA ie_hwnd, &H101&, &H12, &HC0000001 'WM_KEYUP, VK_MENU
End Sub
And it's also possible to click on Save without the UIAutomationClient library. The trick is to directly work with the accessibility library:
Private Declare PtrSafe Function FindWindowExA Lib "user32.dll" ( _
ByVal hwndParent As LongPtr, _
ByVal hwndChildAfter As LongPtr, _
ByVal lpszClass As String, _
ByVal lpszWindow As String) As Long
Private Declare PtrSafe Function AccessibleObjectFromWindow Lib "oleacc.dll" ( _
ByVal Hwnd As LongPtr, _
ByVal dwId As Long, _
ByRef riid As Any, _
ByRef ppvObject As IAccessible) As Long
Private Declare PtrSafe Function AccessibleChildren Lib "oleacc.dll" ( _
ByVal paccContainer As IAccessible, _
ByVal iChildStart As Long, _
ByVal cChildren As Long, _
ByRef rgvarChildren As Variant, _
ByRef pcObtained As Long) As Long
Private Sub ClickOnSave()
Const timeout = 5000, bt_save = "Save", bt_close = "Close"
' wait for the download dialogue (IEFrame/Frame Notification Bar/DirectUIHWND)
Dim ie_hwnd, frm_hwnd, dlg_hwnd, endtime#, i&
ie_hwnd = FindWindowExA(0, 0, "IEFrame", vbNullString)
endtime = Now + timeout / 86400#
Do
frm_hwnd = FindWindowExA(ie_hwnd, 0, "Frame Notification Bar", vbNullString)
If frm_hwnd Then
dlg_hwnd = FindWindowExA(frm_hwnd, 0, "DirectUIHWND", vbNullString)
If dlg_hwnd Then Exit Do
End If
If Now > endtime Then Err.Raise 5, , "Failed to find the download dialogue"
For i = 1 To 10000: DoEvents: Next
Loop
' get the accessible interface for the download dialogue
Dim iid&(0 To 3), acc As IAccessible, bt As IAccessible
iid(0) = &H618736E0: iid(1) = &H11CF3C3D: iid(2) = &HAA000C81: iid(3) = &H719B3800
AccessibleObjectFromWindow dlg_hwnd, 0&, iid(0), acc
' click on Save
Set bt = acc_find_button(acc, bt_save)
If bt Is Nothing Then Err.Raise 5, , "Failed to find the Save button"
bt.accDoDefaultAction 0&
' clic on Close if present
Set bt = acc_find_button(acc, bt_close)
If Not bt Is Nothing Then bt.accDoDefaultAction 0&
End Sub
Private Function acc_find_button(ByVal acc As IAccessible, name$) As IAccessible
If acc.accName(0&) Like name Then
Set acc_find_button = acc
ElseIf acc.accChildCount > 0 Then
Dim children(0 To 15), count&, i&
AccessibleChildren acc, 0, acc.accChildCount, children(0), count
For i = 0 To count - 1
If IsObject(children(i)) Then
Set acc_find_button = acc_find_button(children(i), name)
If Not acc_find_button Is Nothing Then Exit For
End If
Next
End If
End Function
Note that all the provided examples were implemented for Windows8 / IE11
Hi @florentbr I'm interesting in automatically download files from EI11 without direct link and i've test both your script but they can't find the button. I'm testing them on EI11 with text in Italian. Do you know how can i solve the problem ? thanks
Hi Florent,
Is there a way to change the download folder for IEDriver?
As with Chrome:
driver.SetPreference "download.default_directory", sDownloadFolder