VBA code to wait until file download from IE is complete - vba

I'm trying to download an excel file from a webpage and so far I was able to open the webpage, navigate and click on save button but I need to access that excel file once it is downloaded. But sometimes it takes time to download depending on the size of the file. Is there any way we can check the window and see if the download is complete and only then to proceed to open the downloaded file. Below is the code.
Dim o As IUIAutomation
Dim e As IUIAutomationElement
Set o = New CUIAutomation
h = IE.hwnd
h = FindWindowEx(h, 0, "Frame Notification Bar", vbNullString)
If h = 0 Then
MsgBox "Not Found"
End If
Set e = o.ElementFromHandle(ByVal h)
Dim iCnd As IUIAutomationCondition
Set iCnd = o.CreatePropertyCondition(UIA_NamePropertyId, "Save")
Dim Button As IUIAutomationElement
Set Button = e.FindFirst(TreeScope_Subtree, iCnd)
Dim InvokePattern As IUIAutomationInvokePattern
Set InvokePattern = Button.GetCurrentPattern(UIA_InvokePatternId)
InvokePattern.Invoke
The above code will save the download file

This code uses a similar technique to what you have, started, but in addition it will wait for the "Open folder" button to appear in the 'Frame Notification Bar', which will indicate the download is finished. Then it looks in the User's Download folder for a 'very recently added' file and moves it to the place you select. The Code has some Debug.Print statements for error messages, which you may want to change/remove.
Hope this works for you....
Option Explicit
'--Given an IE browser object with the yellow 'Frame Notification Bar' to download file and a File Name to save the downloaded file to,
'--This Sub will use UIAutomation to click the Save button, then wiat for the Open button, then look in the User Downloads folder
'--to get the file just downloaded, then move it to the full file name path given in Filename, then close the 'Frame Notification Bar'
'--DownloadFromIEFrameNotificationBar will return the following codes:
'-- -1 - could not find the Close button in the 'Frame Notification Bar', but file saved OK
'-- 0 - succesfully downloaded and save file
'-- 1 - could not find the 'Frame Notification Bar'
'-- 2 - could not find the Save button in the 'Frame Notification Bar'
'-- 3 - could not find the 'Open folder' button in the 'Frame Notification Bar'
'-- 4 - could not find Very recent file (Last modified within 3 seconds) in the User Downloads folder
Public Function DownloadFromIEFrameNotificationBar(ByRef oBrowser As InternetExplorer, Filename As String) As Long
Dim UIAutomation As IUIAutomation
Dim eBrowser As IUIAutomationElement, eFNB As IUIAutomationElement, e As IUIAutomationElement
Dim InvokePattern As IUIAutomationInvokePattern
Dim DLfn As String
DownloadFromIEFrameNotificationBar = 0
Set UIAutomation = New CUIAutomation
Set eBrowser = UIAutomation.ElementFromHandle(ByVal oBrowser.hwnd)
'--Find 'Frame Notification Bar' element
Set eFNB = FindFromAllElementsWithClassName(eBrowser, "Frame Notification Bar", 10)
If eFNB Is Nothing Then
Debug.Print "'Frame Notification Bar' not found"
DownloadFromIEFrameNotificationBar = 1
Exit Function
End If
'--Find 'Save' button element
Set e = FindFromAllElementWithName(eFNB, "Save")
If e Is Nothing Then
Debug.Print "'Save' button not found"
DownloadFromIEFrameNotificationBar = 2
Exit Function
End If
'--'Click' the 'Save' button
Sleep 100
Set InvokePattern = e.GetCurrentPattern(UIA_InvokePatternId)
InvokePattern.Invoke
'--Wait for the file to download by waiting for the 'Open Folder' button to appear in the 'Frame Notification Bar'
Set e = FindFromAllElementWithName(eFNB, "Open folder", 15)
If e Is Nothing Then
Debug.Print "'Open Folder' button not found"
DownloadFromIEFrameNotificationBar = 3
Exit Function
End If
'--Done with download, now look for a file that was very recently (with in 3 seconds) added to the User's Downloads folder and get the file name of it
DLfn = FindVeryRecentFileInDownloads()
If DLfn <> "" Then
'--We got recent downloaded file, now Delete the file we are saving too (if it exists) so the Move file will be successful
DeleteFile Filename
MoveFile DLfn, Filename
Else
Debug.Print "Very recent file not found!"
DownloadFromIEFrameNotificationBar = 4
End If
'--Close Notification Bar window
Set e = FindFromAllElementWithName(eFNB, "Close")
If e Is Nothing Then
Debug.Print "'Close' button not found"
DownloadFromIEFrameNotificationBar = -1
Exit Function
End If
'--'Click' the 'Close' button
Sleep 100
Set InvokePattern = e.GetCurrentPattern(UIA_InvokePatternId)
InvokePattern.Invoke
End Function
Private Function FindFromAllElementWithName(e As IUIAutomationElement, n As String, Optional MaxTime As Long = 5) As IUIAutomationElement
Dim oUIAutomation As New CUIAutomation
Dim ea As IUIAutomationElementArray
Dim i As Long, timeout As Date
timeout = Now + TimeSerial(0, 0, MaxTime)
Do
Set ea = e.FindAll(TreeScope_Subtree, oUIAutomation.CreateTrueCondition)
For i = 0 To ea.length - 1
If ea.GetElement(i).CurrentName = n Then
Set FindFromAllElementWithName = ea.GetElement(i)
Exit Function
End If
Next
DoEvents
Sleep 20
Loop Until Now > timeout
Set FindFromAllElementWithName = Nothing
End Function
Private Function FindFromAllElementsWithClassName(e As IUIAutomationElement, c As String, Optional MaxTime As Long = 5) As IUIAutomationElement
Dim oUIAutomation As New CUIAutomation
Dim ea As IUIAutomationElementArray
Dim i As Long, timeout As Date
timeout = Now + TimeSerial(0, 0, MaxTime)
Do
Set ea = e.FindAll(TreeScope_Subtree, oUIAutomation.CreateTrueCondition)
For i = 0 To ea.length - 1
If ea.GetElement(i).CurrentClassName = c Then
Set FindFromAllElementsWithClassName = ea.GetElement(i)
Exit Function
End If
Next
DoEvents
Sleep 20
Loop Until Now > timeout
Set FindFromAllElementsWithClassName = Nothing
End Function
Private Function FindVeryRecentFileInDownloads(Optional MaxSecs As Long = 3) As String
Dim fso As New FileSystemObject, f As File, First As Boolean, lfd As Date, Folder As String
Dim WS As Object
On Error GoTo errReturn
Set WS = CreateObject("WScript.Shell")
'--Get Current user's Downloads folder path
Folder = WS.RegRead("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders\{374DE290-123F-4565-9164-39C4925E467B}")
First = True
For Each f In fso.GetFolder(Folder).Files
If First Then
lfd = f.DateLastModified
FindVeryRecentFileInDownloads = f.Path
First = False
ElseIf f.DateLastModified > lfd Then
lfd = f.DateLastModified
FindVeryRecentFileInDownloads = f.Path
End If
Next
If First Then
FindVeryRecentFileInDownloads = "" '--no files
ElseIf MaxSecs <> -1 And DateDiff("s", lfd, Now) > MaxSecs Then
FindVeryRecentFileInDownloads = "" '--no very recent file found
End If
Exit Function
errReturn:
FindVeryRecentFileInDownloads = ""
End Function
Private Sub MoveFile(SourcePath As String, DestinationPath As String)
Dim fso As New FileSystemObject
CreateCompletePath Left(DestinationPath, InStrRev(DestinationPath, Application.PathSeparator))
fso.MoveFile SourcePath, DestinationPath
End Sub
Public Sub CreateCompletePath(sPath As String)
Dim iStart As Integer
Dim aDirs As Variant
Dim sCurDir As String
Dim i As Integer
sPath = Trim(sPath)
If sPath <> "" And Dir(sPath, vbDirectory) = vbNullString Then
aDirs = Split(sPath, Application.PathSeparator)
If Left(sPath, 2) = Application.PathSeparator & Application.PathSeparator Then
iStart = 3
Else
iStart = 1
End If
sCurDir = Left(sPath, InStr(iStart, sPath, Application.PathSeparator))
For i = iStart To UBound(aDirs)
If Trim(aDirs(i)) <> vbNullString Then
sCurDir = sCurDir & aDirs(i) & Application.PathSeparator
If Dir(sCurDir, vbDirectory) = vbNullString Then MkDir sCurDir
End If
Next i
End If
End Sub

Related

VBA objShell.windows.title not finding title

I run this function with two IE browsers open. IE_count finds six objects, but it does not find any titles (my_title) within the for loop for objShell. They all return an empty string.
Any idea as to why this could be? Relevant code below:
' code below adapted from ron's answer here: https://stackoverflow.com/questions/21407340/how-to-read-text-from-an-already-open-webpage-using-vba
Function SecondBrowserSearchForAndClick(ElementID As String, searchFor As String)
Dim objShell
Set objShell = CreateObject("Shell.Application")
Dim IE_count As Integer
IE_count = objShell.Windows.Count
Dim x As Integer
For x = 0 To (IE_count - 1)
On Error Resume Next ' sometimes more web pages are counted than are open
Dim my_url As String
my_url = objShell.Windows(x).document.Location
Dim my_title As String
my_title = objShell.Windows(x).document.Title
If my_title Like "*Select Process*" Then 'compare to find if the desired web page is already open
Dim tagColl_TR As Object
Set tagColl_TR = objShell.Windows(x).document.getElementById(ElementID).contentDocument.getElementsByTagName("tr")
Dim f
While f < tagColl_TR.Length
If tagColl_TR(f).Children.Length = 5 Then
If tagColl_TR(f).Children(3).Children(0).innerText Like "*" & searchFor & "*" Then
tagColl_TR(f).Children(1).Children(0).Children(1).Focus
tagColl_TR(f).Children(1).Children(0).Children(1).Click
Exit Function
End If
End If
f = f + 1
Wend
End If
Next
End Function
Any help would be appreciated.
It's easier to put the "find document by title" functionality in its own function:
Sub Tester()
Dim doc As Object
Set doc = IEDocumentByTitle("Google")
If Not doc Is Nothing Then
Debug.Print "Found window at: " & doc.Location
'work on doc here
End If
End Sub
'Return an open IE document based on its Title property
Function IEDocumentByTitle(title As String)
Dim w As Object, ttl As String
For Each w In CreateObject("Shell.Application").Windows
If w.Application.Name = "Internet Explorer" Then 'filter out Windows Explorer
ttl = ""
On Error Resume Next
ttl = w.document.title
On Error GoTo 0
If ttl Like title Then
Set IEDocumentByTitle = w.document
Exit Function
End If
End If
Next w
End Function
This works fine for me.
BTW the shell Windows collection also includes Windows Explorer instances in addition to IE windows/tabs.
Also you should really cancel On Error Resume Next as soon as possible or it will silently swallow all errors in your code, possibly leading to unexpected results.

Tackle the 'Not responding application outside of Microsoft Access' error in the calling Access VBA

I am using the ScriptControl in Access VBA to load the scripts (.vbs files) and execute them for extracting data from a SAP system. For the small data the code works fine.
However, when there is a big data which takes time or stops responding then Access opens a popup window asking me to switch to the app or retry. If I click on retry button or by hand switch to that window, then the script resumes!
Is there any way to tackle this access popup window or a code to press this retry button? Thanks
Mycode:
Open scriptPath For Input As #1
vbsCode = Input$(LOF(1), 1)
Close #1
On Error GoTo ERR_VBS
With CreateObject("ScriptControl")
.Language = "VBScript"
.AddCode vbsCode '>>>>>>>>>>>>>>>> I get this popup window at this line
End With
Tried :
Sub Test()
Dim oSC As Object
Set oSC = CreateObjectx86("ScriptControl") ' create ActiveX via x86 mshta host
Debug.Print TypeName(oSC) ' ScriptControl
' do some stuff
CreateObjectx86 Empty ' close mshta host window at the end
End Sub
Function CreateObjectx86(sProgID)
Static oWnd As Object
Dim bRunning As Boolean
Dim vbsCode As String, result As Variant, Script As Object
Open "\My Documents\\Desktop\x.vbs" For Input As #1
vbsCode = Input$(LOF(1), 1)
Close #1
Set oWnd = CreateWindow()
oWnd.execScript vbsCode, "VBScript" '>>>>>>>>>Gets an Error says "Error on Script page"
Set CreateObjectx86 = oWnd.CreateObjectx86(sProgID)
End Function
Function CreateWindow()
' source http://forum.script-coding.com/viewtopic.php?pid=75356#p75356
Dim sSignature, oShellWnd, oProc
On Error Resume Next
Do Until Len(sSignature) = 32
sSignature = sSignature & Hex(Int(Rnd * 16))
Loop
CreateObject("WScript.Shell").Run "%systemroot%\syswow64\mshta.exe about:""<head><script>moveTo(-32000,-32000);document.title='x86Host'</script><hta:application showintaskbar=no /><object id='shell' classid='clsid:8856F961-340A-11D0-A96B-00C04FD705A2'><param name=RegisterAsBrowser value=1></object><script>shell.putproperty('" & sSignature & "',document.parentWindow);</script></head>""", 0, False
Do
For Each oShellWnd In CreateObject("Shell.Application").Windows
Set CreateWindow = oShellWnd.GetProperty(sSignature)
If Err.Number = 0 Then Exit Function
Err.Clear
Next
Loop
End Function
So after lot of headache, I found the solution! The solution is to use waitToReturn. This will make Access VBA wait for the Script to be completed no matter how long it take! Hence, this tackled the problem of Access popup window asking to switch to window or Retry!
Solution code:
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
Dim waitOnReturn As Boolean: waitOnReturn = True
Dim windowStyle As Integer: windowStyle = 1
Dim errorCode As Integer
errorCode = wsh.Run("C:\path\x.vbs", windowStyle, waitOnReturn)
If errorCode = 0 Then
MsgBox "Script successful. "
Else
MsgBox "Script exited with error code " & errorCode & "."
End If
with cases like this you would always try to get the focus via the object you are manipulating, usually it is done by .setFocus or .active.
the below is code that will help you out. I would try the session.setFocus.
Session.ActiveWindow.SetFocus
the below code will also help:
Dim SapGuiAuto As Object
Dim Application As SAPFEWSELib.GuiApplication
Dim Connection As SAPFEWSELib.GuiConnection
Dim Session As SAPFEWSELib.GuiSession
Dim UserArea As SAPFEWSELib.GuiUserArea
' Dim oWindow As SAPFEWSELib.GuiConnection
Dim oUserAreaOfMobileWindow As SAPFEWSELib.GuiUserArea
Dim oGuiSimpleContainer As SAPFEWSELib.GuiSimpleContainer
Set SapGuiAuto = GetObject("SAPGUI")
If Not IsObject(SapGuiAuto) Then
Exit Sub
End If
Set Application = SapGuiAuto.GetScriptingEngine()
If Not IsObject(Application) Then
Exit Sub
End If
Set Connection = Application.Connections(0)
If Not IsObject(Connection) Then
Exit Sub
End If
Set Session = Connection.Sessions(0)
If Not IsObject(Session) Then
Exit Sub
End If

select outlook mail folder using Outlook VBA

I have created a VBA subroutine to list any and all sub-folders that have "NNN" text in the name in a list-box on a userform - I have loads of sub-folders and finding the right one is therefore time consuming. This routine works perfectly.
However, what I now want to do is to double-click on a list-box item and it "selects" the folder in the folder hierarchy to save me the time to locate it manually (it could be several levels down).
I have a snippet that does this:
Public Sub GetItemsFolderPath()
Dim obj As Object
Dim F As Outlook.MAPIFolder
Dim Msg$
Set obj = Application.ActiveWindow
If TypeOf obj Is Outlook.Inspector Then
Set obj = obj.CurrentItem
Else
Set obj = obj.Selection(1)
End If
Set F = obj.Parent
Msg = "The path is: " & F.FolderPath & vbCrLf
Msg = Msg & "Switch to the folder?"
If MsgBox(Msg, vbYesNo) = vbYes Then
Set Application.ActiveExplorer.CurrentFolder = F
End If
End Sub
However, if I try and replace "F" with a folder path which is just a string, it fails.
So my question is, how can I select the folder using just a string for the folder path like "paul#anymail.com\Inbox\03_Group Finance\00_Organization Chart"
Thanks
I tried this little simple thing to return a folder from a path:
Function FolderFromPath(FolderPath As String) As Folder
Dim F As Folder
Dim arrFolders() As String
Dim i As Integer
Set myNamespace = Application.GetNamespace("MAPI")
Set F = myNamespace.GetDefaultFolder(olFolderInbox)
arrFolders = Split(FolderPath, "\")
For i = 4 To UBound(arrFolders)
Set F = F.Folders(arrFolders(i))
Next
Set FolderFromPath = F
End Function
It starts from your inbox (which perhaps isn't what you need), and then splits the path and goes into each folder in the path.
Update after comment
I forgot to show how to use it. You can do it like this:
Path = "\\first.last#company.com\Inbox\Folder1\Folder2"
Set Application.ActiveExplorer.CurrentFolder = FolderFromPath(Path)
The method described by Sam will do what you want. There is a small problem with the code. The index starts to far along the path. 4 should be 2 if the initial reference is to the Inbox.
Function FolderFromPath(FolderPath As String) As Folder
Dim F As Folder
Dim arrFolders() As String
Dim i As Long
arrFolders = Split(FolderPath, "\")
' Initial reference is to the mailbox - array element 0
Set F = Session.Folders(arrFolders(0))
' The next folder is array element 1
For i = LBound(arrFolders) + 1 To UBound(arrFolders)
Set F = F.Folders(arrFolders(i))
Next
Set FolderFromPath = F
End Function
Public Sub GetItemsFolderPath_Test()
Dim FPath As String
FPath = "paul#anymail.com\Inbox\03_Group Finance\00_Organization Chart"
Set ActiveExplorer.CurrentFolder = FolderFromPath(FPath)
End Sub

How do I speed up searching through files on a network drive and prevent "not responding" in VB.Net

Part of my application searches for a string provided by a textbox within .txt files and .pdf files stored on a network drive. It works great on my PC when looking within a directory of a few files stored on a local disk, when I run it on my work PC which is less beefy and looking at a network drive I get "not responding" and it takes a good few minutes to finally wake up again. This is only searching through around 100 files and that number is only going to get bigger.
I thought about adding a progress bar, but if the application is not responding I assume the progress bar will not update? I would be very grateful to hear the advice of someone much more experienced in this than me.
In short, the code searches as described above, uses the name of the folder (that begins with SCC) to add an item to a listbox. If it cannot search the PDF for any reason then it adds it to another listbox displaying the folder names it was unable to search.
Here is the section of code that does the search:
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim path As String = ""
Dim list As System.Collections.ObjectModel.ReadOnlyCollection(Of String)
Dim raf As itextsharp.text.pdf.RandomAccessFileOrArray = Nothing
Dim reader As itextsharp.text.pdf.PdfReader = Nothing
Dim searchPhrase As String = faultSearch.Text
VINListBox.Items.Clear()
Region "search plain text"
'finds text within faultSearch textbox within the files in directory
list = My.Computer.FileSystem.FindInFiles(archive_dir, faultSearch.Text, True, FileIO.SearchOption.SearchAllSubDirectories)
For Each foundFile As String In list
path = foundFile
GetVinName(path)
Next
End Region
Region "search PDF"
'Finds text within faultSearch textbox within PDF files in directory
For Each file As String In My.Computer.FileSystem.GetFiles(archive_dir, FileIO.SearchOption.SearchAllSubDirectories)
If System.IO.Path.GetExtension(file).ToUpper = ".PDF" Then
Try
raf = New itextsharp.text.pdf.RandomAccessFileOrArray(file)
reader = New itextsharp.text.pdf.PdfReader(raf, Nothing)
For i As Integer = 1 To reader.NumberOfPages()
Dim pageText As String = itextsharp.text.pdf.parser.PdfTextExtractor.GetTextFromPage(reader, i)
If pageText.ToUpper.Contains(searchPhrase) Then
GetVinName(file)
End If
Next
Catch ex As Exception
GetErrVinName(file)
End Try
End If
Next
End Sub
End Region
Private Sub GetVinName(ByVal strVIN As String)
Dim fldName() As String
fldName = Split(strVIN.ToString, "\",, CompareMethod.Text)
For x = 0 To UBound(fldName) - 1
If InStr(fldName(x), "SCC", CompareMethod.Text) > 0 Then
strVIN = Trim(fldName(x))
If errListBox.Items.Contains(strVIN) = True Then
errListBox.Items.Remove(strVIN)
End If
If VINListBox.Items.Contains(strVIN) = False Then
VINListBox.Items.Add(strVIN)
End If
Exit For
End If
Next
End Sub
Private Sub GetErrVinName(ByVal strVIN As String)
Dim fldName() As String
fldName = Split(strVIN.ToString, "\",, CompareMethod.Text)
For x = 0 To UBound(fldName) - 1
If InStr(fldName(x), "SCC", CompareMethod.Text) > 0 Then
strVIN = Trim(fldName(x))
If VINListBox.Items.Contains(strVIN) = False Then
If errListBox.Items.Contains(strVIN) = False Then
errListBox.Items.Add(strVIN)
Else
Exit Sub
End If
Else
Exit Sub
End If
Exit For
End If
Next
End Sub

Close all IE windows except 1 in VBA

I'm using the following code to close all IE windows. This is part of error handling, when my larger function hangs or crashes, it automatically starts a new window. When that happens, I want to close all IE windows, except 1 (I want to keep that last one open to maintain the session, so the browser doesn't have to re-authenticate when it opens again).
Can I update the loop below so it closes all IE windows except for the last one (it doesn't matter which specific one it leaves open, just that it leaves one open).
Function closeallIE()
Dim objWMI As Object, objProcess As Object, objProcesses As Object
Set objWMI = GetObject("winmgmts://.")
Set objProcesses = objWMI.ExecQuery( _
"SELECT * FROM Win32_Process WHERE Name = 'iexplore.exe'")
For Each objProcess In objProcesses
Call objProcess.Terminate
Next
Set objProcesses = Nothing: Set objWMI = Nothing
Debug.Print wait(3)
End Function
You could use the Count property and use the PID with Taskkill:
Sub closeallIE()
Dim objWMI As Object, objProcess As Object, objProcesses As Object
Set objWMI = GetObject("winmgmts://.")
Set objProcesses = objWMI.ExecQuery( _
"SELECT * FROM Win32_Process WHERE Name = 'iexplore.exe'")
Dim j As Integer
j = objProcesses.Count
For Each objProcess In objProcesses
If j > 1 Then Shell "taskkill /f /PID " & CStr(objProcess.ProcessID), vbHide
j = j - 1
Next
Set objProcesses = Nothing
Set objWMI = Nothing
End Sub
This code snippet taken from HP UFT Website regarding closing browser tabs.
This works without any tweaks and uses creation time to close all tabs except the oldest by creation time.
On error resume next
Set oDesc = Description.Create
oDesc( "micclass" ).Value = "Browser"
oDesc( "application version" ).Value = browserPropertyApplicationVersion
Set vIE= Desktop.ChildObjects(oDesc)
vIECount=vIE.count
z=vIECount
For m=1To vIECount - 1
z=z-1
If Browser("creationtime:="&z).Exist(0) Then
Browser("creationtime:=1").Close
End If
If err.number > 0 Then
message = message & " #######Error shown by application is : " & err.description
icon_flag=0
End If
Next
Set oDesc = Nothing
Set vIE=Nothing