Best Method For Excel VBA to Get Commands From Central Server - vba

I'm sure something like this has been asked before but I guess I'm not searching the right keywords because I couldn't find a good answer.
I have created an Excel Add-In used by my entire team. I keep the most recent version on the Network drive and whenever someone re-opens Excel, the add-in checks if there is a new version and updates itself automatically.
What I'd like to do is be able to send commands to the add-ins individually to execute. For instance, if I have an important update to push, rather than waiting for each user to re-open Excel, I'd like to be able to save the command on the Network drive in a text file (i.e. "USER: ALL; COMMAND: UPDATE") and each user's add-in would automatically pick-up that command and process it within a reasonable time frame.
My question is what's the best method for accomplishing this? I can think of two solutions off the top of my head, neither of which I like.
Potential Solution #1 - In 'Worksheet_Calculate' or some similar place, have it check for new commands and process any it finds. However that seems like overkill and would potentially be checking far too often.
Potential Solution #2 - Use an infinite chain of Application.OnTime calls so that every X seconds/minutes it's checking for new central commands and will process any it finds. However I find Application.OnTime to be funky and unreliable.
Any ideas? I feel like doing something with a Class is the way to go but I don't have much experience with those.
Thanks!

OK, I ended up going with Potential Solution #1.
Code in ThisWorkbook
Private Sub Workbook_SheetCalculate(ByVal Sh As Object)
If mdtLastCheck = 0 Or DateDiff("s", mdtLastCheck, Now) > miCHECK_FREQUENCY_SECONDS Then
mdtLastCheck = Now
CheckForCommandsAndRun
End If
End Sub
Code in MCentralCommands
Note the only reference in this module to other modules is to a couple of global variables like gsAPP_MASTER_PATH. This code uses the MErrorHandler system from this book: Professional Excel Development.
Option Explicit
' Description: This module contains
'
Private Const msModule As String = "MCentralCommands"
Private Const msCOMMANDS_FOLDER As String = "Commands\"
Private Const msCOMMAND_NAME_FORUSER As String = "CMD_USERNAME_*"
Private Const msCOMMAND_NAME_FORALL As String = "CMD_ALL_*"
Public Const miCHECK_FREQUENCY_SECONDS = 10
Public mdtLastCheck As Date
Sub CheckForCommandsAndRun()
' *********************************************
' Entry-Point Procedure Code Start
' *********************************************
Const sSource As String = "CheckForCommandsAndRun"
On Error GoTo ErrorHandler
' *********************************************
' *********************************************
Dim sCommands() As String
If Not bGetNewCommands(sCommands) Then Err.Raise glHANDLED_ERROR
If Not bProcessAllCommands(sCommands) Then Err.Raise glHANDLED_ERROR
' *********************************************
' Entry-Point Procedure Code Exits
' *********************************************
ErrorExit:
Exit Sub
ErrorHandler:
If bCentralErrorHandler(msModule, sSource, , True) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Sub
Private Function bGetNewCommands(sCommands() As String) As Boolean
' *********************************************
' **** Function Code Start
' *********************************************
Dim bReturn As Boolean
Const sSource As String = "bGetNewCommands()"
On Error GoTo ErrorHandler
bReturn = True
' *********************************************
' *********************************************
Dim sCommandPath As String, sUser As String
sCommandPath = gsAPP_MASTER_PATH & msCOMMANDS_FOLDER
sUser = UCase(Application.UserName)
Dim iCommandCount As Integer
Dim vFile As Variant
vFile = Dir(sCommandPath)
While (vFile <> "")
If vFile Like msCOMMAND_NAME_FORALL Or _
vFile Like Replace(msCOMMAND_NAME_FORUSER, "USERNAME", sUser) Then _
ReDim Preserve sCommands(0 To iCommandCount)
sCommands(iCommandCount) = vFile
iCommandCount = iCommandCount + 1
End If
vFile = Dir
Wend
' *********************************************
' Function Code Exits
' *********************************************
ErrorExit:
bGetNewCommands = bReturn
Exit Function
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msModule, sSource) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function
Private Function bProcessAllCommands(sCommands() As String) As Boolean
' *********************************************
' **** Function Code Start
' *********************************************
Dim bReturn As Boolean
Const sSource As String = "bProcessAllCommands()"
On Error GoTo ErrorHandler
bReturn = True
' *********************************************
' *********************************************
Dim sCommandPath As String, sUser As String
sCommandPath = gsAPP_MASTER_PATH & msCOMMANDS_FOLDER
sUser = UCase(Application.UserName)
Dim iCmd As Integer
For iCmd = LBound(sCommands) To UBound(sCommands)
If Not bProcessCommand(sCommands(iCmd)) Then Err.Raise glHANDLED_ERROR
Next
' *********************************************
' Function Code Exits
' *********************************************
ErrorExit:
bProcessAllCommands = bReturn
Exit Function
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msModule, sSource) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function
Private Function bProcessCommand(sCommand As String, Optional bDeleteIfUserCmd As Boolean = True) As Boolean
' *********************************************
' **** Function Code Start
' *********************************************
Dim bReturn As Boolean
Const sSource As String = "bProcessCommand()"
On Error GoTo ErrorHandler
bReturn = True
' *********************************************
' *********************************************
Dim sCommandPath As String, sUser As String
sCommandPath = gsAPP_MASTER_PATH & msCOMMANDS_FOLDER
sUser = UCase(Application.UserName)
Dim bHaveIRun As Boolean, bCommandSuccessful As Boolean
If Not bHaveIRunCommand(sCommand, bHaveIRun) Then Err.Raise glHANDLED_ERROR
If Not bHaveIRun Then
If Not bRunCommand(sCommand, bCommandSuccessful) Then Err.Raise glHANDLED_ERROR
If bCommandSuccessful Then
If Not bMarkCommandAsRan(sCommand) Then Err.Raise glHANDLED_ERROR
MLog.Log "Ran: " & sCommand
End If
End If
' *********************************************
' Function Code Exits
' *********************************************
ErrorExit:
bProcessCommand = bReturn
Exit Function
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msModule, sSource) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function
Private Function bRunCommand(sCommand As String, bCommandSuccessful As Boolean) As Boolean
' *********************************************
' **** Function Code Start
' *********************************************
Dim bReturn As Boolean
Const sSource As String = "bRunCommand()"
On Error GoTo ErrorHandler
bReturn = True
' *********************************************
' *********************************************
Dim sCommandName As String
sCommandName = Replace(Mid(sCommand, InStrRev(sCommand, "_") + 1), ".txt", "")
Select Case UCase(sCommandName)
Case "MSGBOX":
Dim sMsgBoxText As String
If Not bGetParameterFromCommand(sCommand, "Msg", sMsgBoxText) Then Err.Raise glHANDLED_ERROR
MsgBox sMsgBoxText
bCommandSuccessful = True
Case "UPDATE":
CheckForUpdates False
bCommandSuccessful = True
Case "OLFLDRS":
UpdateSavedOutlookFolderList
bCommandSuccessful = True
End Select
' *********************************************
' Function Code Exits
' *********************************************
ErrorExit:
bRunCommand = bReturn
Exit Function
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msModule, sSource) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function
Private Function bGetParameterFromCommand(sCommand As String, sParameterName As String, sParameterReturn As String) As Boolean
' *********************************************
' **** Function Code Start
' *********************************************
Dim bReturn As Boolean
Const sSource As String = "bGetParameterFromCommand()"
On Error GoTo ErrorHandler
bReturn = True
' *********************************************
' *********************************************
Dim sCommandPath As String, sUser As String
sCommandPath = gsAPP_MASTER_PATH & msCOMMANDS_FOLDER
sUser = UCase(Application.UserName)
Dim sFilePath As String, sParameterText() As String, sTextLine As String
Dim iLineCount As Integer
sFilePath = sCommandPath & sCommand
Dim bBegin As Boolean
Open sFilePath For Input As #1
Do Until EOF(1)
Line Input #1, sTextLine
If bBegin Then If Left(sTextLine, 1) = ":" Then bBegin = False
If sTextLine Like "*:Parameters:*" Then
bBegin = True
End If
If bBegin Then
ReDim Preserve sParameterText(0 To iLineCount)
sParameterText(iLineCount) = sTextLine
iLineCount = iLineCount + 1
End If
Loop
Close #1
Dim iParameterCounter As Integer
For iParameterCounter = LBound(sParameterText) To UBound(sParameterText)
If sParameterText(iParameterCounter) Like sParameterName & ": *" Then _
sParameterReturn = Mid(sParameterText(iParameterCounter), InStr(1, sParameterText(iParameterCounter), " ") + 1)
Next
' *********************************************
' Function Code Exits
' *********************************************
ErrorExit:
bGetParameterFromCommand = bReturn
Exit Function
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msModule, sSource) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function
Private Function bHaveIRunCommand(sCommand As String, bHaveIRun As Boolean) As Boolean
' *********************************************
' **** Function Code Start
' *********************************************
Dim bReturn As Boolean
Const sSource As String = "bHaveIRunCommand()"
On Error GoTo ErrorHandler
bReturn = True
' *********************************************
' *********************************************
Dim sCommandPath As String, sUser As String
sCommandPath = gsAPP_MASTER_PATH & msCOMMANDS_FOLDER
sUser = UCase(Application.UserName)
Dim sFile As String, sText As String, sTextLine As String
sFile = sCommandPath & sCommand
Dim bBegin As Boolean
Open sFile For Input As #1
Do Until EOF(1)
Line Input #1, sTextLine
If bBegin Then If Left(sTextLine, 1) = ":" Then bBegin = False
If sTextLine Like "*:Run By Users:*" Then bBegin = True
If bBegin Then
sText = sText & sTextLine
End If
Loop
Close #1
bHaveIRun = sText Like "*" & sUser & "*"
' *********************************************
' Function Code Exits
' *********************************************
ErrorExit:
bHaveIRunCommand = bReturn
Exit Function
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msModule, sSource) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function
Private Function bMarkCommandAsRan(sCommand As String) As Boolean
' *********************************************
' **** Function Code Start
' *********************************************
Dim bReturn As Boolean
Const sSource As String = "bMarkCommandAsRan()"
On Error GoTo ErrorHandler
bReturn = True
' *********************************************
' *********************************************
Dim sCommandPath As String, sUser As String
sCommandPath = gsAPP_MASTER_PATH & msCOMMANDS_FOLDER
sUser = UCase(Application.UserName)
Dim sFilePath As String, sRanText As String, sTextLine As String, bHaveIRun As Boolean
Dim sFullText() As String, iLineCount As Integer, iRunBy As Integer
sFilePath = sCommandPath & sCommand
Dim bBegin As Boolean
Open sFilePath For Input As #1
Do Until EOF(1)
Line Input #1, sTextLine
ReDim Preserve sFullText(0 To iLineCount)
sFullText(iLineCount) = sTextLine
iLineCount = iLineCount + 1
If bBegin Then If Left(sTextLine, 1) = ":" Then bBegin = False
If sTextLine Like "*:Run By Users:*" Then
bBegin = True
iRunBy = iLineCount - 1
End If
If bBegin Then
sRanText = sRanText & sTextLine
End If
Loop
Close #1
bHaveIRun = sRanText Like "*" & sUser & "*"
If Not bHaveIRun Then
Dim iCounter As Integer
Open sFilePath For Output As #1
For iLineCount = LBound(sFullText) To UBound(sFullText)
Print #1, sFullText(iLineCount)
If iLineCount = iRunBy Then _
Print #1, sUser
Next
Close #1
End If
' *********************************************
' Function Code Exits
' *********************************************
ErrorExit:
bMarkCommandAsRan = bReturn
Exit Function
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msModule, sSource) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function

Related

Update of MP3 Tags in Access

Hello everyone I attempted to run the following code to input the MP3 Tags from the MP3Properties table into the Tracks in a folder and upon doing so I receive the following error Compile error: Function call on left-hand side of assignment must return variant or object in the SetID3Tag function: How can I fix this issue? Thanks
Dim RS As DAO.Recordset
Set RS = CurrentDb.OpenRecordset("SELECT * FROM MP3Properties")
If Not (RS.EOF And RS.BOF) Then
RS.MoveFirst
Do Until RS.EOF = True
Dim Tag As ID3Tag
Tag.Header = "TAG"
Tag.SongTitle = RS!Title
Tag.Album = RS!Album
Tag.Genre = RS!Genre
Tag.Artist = RS!Artist
Tag.Year = RS!Year
Call SetID3Tag(Forms![UpdateMp3]![Text2], Tag)
'Call SetID3Tag(Forms![UpdateMp3]![Text2], ((RS!Album)))
'Call SetID3Tag(Forms![UpdateMp3]![Text2], ((RS!Title)))
'Call SetID3Tag(Forms![UpdateMp3]![Text2], ((RS!Genre)))
'Call SetID3Tag(Forms![UpdateMp3]![Text2], ((RS!Name)))
'Call SetID3Tag(Forms![UpdateMp3]![Text2], ((RS![Album Artist])))
RS.MoveNext
Loop
Else
MsgBox "There are no records in the recordset."
End If
RS.Close
Set RS = Nothing
'Dim TblCnt As Long
'TblCnt = DCount("*", "MP3Properties")
'Call GetID3Tag(Forms![UpdateMp3]![Text2], 237)
'Call SetID3Tag(Forms![UpdateMp3]![Text2], 237)
Public Function SetID3Tag(ByVal filename As String, Tag As ID3Tag) As Boolean
On Error GoTo SetID3TagError
Dim FileNum As Long
If Dir(filename) = "" Then
SetID3Tag = False
Exit Function
End If
Tag.Header = "TAG"
FileNum = FreeFile
Open filename For Binary As FileNum
Put FileNum, LOF(1) - 128, Tag
Close FileNum
SetID3TagDirect = True
Exit Function
SetID3TagError:
Close FileNum
SetID3Tag = False
End Function
Public Function SetID3TagDirect(ByVal filename As String, _
ByVal Artist_30 As String, ByVal SongTitle_30 As String, _
ByVal Album_30 As String, ByVal Comment_30 As String, _
ByVal Year_4 As String, ByVal Genre_B255 As Byte) As Boolean
Dim Tag As ID3Tag
'Debug.Print MyModule
On Error GoTo SetID3TagDirectError
Dim FileNum As Long
If Dir(filename) = "" Then
SetID3TagDirect = False
Exit Function
End If
Tag.Header = "TAG"
Tag.Artist = Artist_30
Tag.SongTitle = SongTitle_30
Tag.Album = Album_30
Tag.Comment = Comment_30
Tag.Year = Year_4
Tag.Genre = Genre_B255
FileNum = FreeFile
Open filename For Binary As FileNum
Put FileNum, LOF(1) - 127, Tag
Close FileNum
SetID3TagDirect = True
Exit Function
SetID3TagDirectError:
Close FileNum
SetID3TagDirect = False
End Function

Compile Error - Argument Not Optional

I am getting error as Compile Error: Argument Not Optional when running vba code pointing towards the line. MsgBox (RemoveFirstChar)
Code:
Sub test()
Dim Currworkbook As Workbook
Dim CurrWKSHT As Worksheet
Dim Filename As String
Dim BCName As String
Dim Str As String
FFolder = "C:\user"
CurrLoc = "File3"
If CurrrLoc = "File3" Then
CurrLoc = FFolder & "\" & CurrLoc
Set FSobj = CreateObject("Scripting.FileSystemObject")
On Error Resume Next
Set FFolderObj = FSobj.GetFolder(CurrLoc)
If Err.Number > 0 Then
'
End If
For Each BCObj In FFolderObj.Files
'BCName = Right(BCObj.Name, (Len(BCObj.Name) - InStrRev(BCObj.Name, "\", 1)))
If IsNumeric(Left(BCObj.Name, 4)) <> True Then
Call RemoveFirstChar(BCObj.Name)
'Str = RemoveFirstChar
MsgBox (RemoveFirstChar) '--->Error: Compile Error: Argument Not Optional
Else
MsgBox (BCObj.Name)
End If
Next
End If
End Sub
Public Function RemoveFirstChar(RemFstChar As String) As String
Dim TempString As String
TempString = RemFstChar
If Left(RemFstChar, 1) = "1" Then
If Len(RemFstChar) > 1 Then
TempString = Right(RemFstChar, Len(RemFstChar) - 1)
End If
End If
RemoveFirstChar = TempString
End Function
RemoveFirstChar is a user defined function that requires a non-optional string as a parameter.
Public Function RemoveFirstChar(RemFstChar As String) As String
....
End Function
I think you want to get rid of the Call RemoveFirstChar(BCObj.Name) then use,
MsgBox RemoveFirstChar(BCObj.Name)

Exclude images with specific name

I have a VBA code that pulls images and inserts in Excel file based on cell value in column A. But in my P drive, from where it pulls images, I have images that end with ' -TH ' and I want to exclude them. i.e. I have image in P drive, that named as "CITY-B" and the other one as "CITY-B-TH". And when I type 'CITY'(this is how I need the name to be typed in excel), I want it to insert the one without "TH". How can i do that?
Private Sub Worksheet_Change(ByVal Target As Range)
If (Split(Target.Address, "$")(1) <> "A") Then Exit Sub
Call Inser_Image(Target)
End Sub
Private Sub Inser_Image(Ac_Cells As Range)
Dim myRng As Range
Dim Mycell As Range
Dim St As String
Dim myPath As String
Dim My_Pic As Shape
Dim My_File As String
Dim Ac_cell As Range
myPath = Sheet1.Cells(1, 5).Value
If Len(myPath) > 3 Then
If Right(myPath, 1) <> "\" Then
myPath = myPath + "\"
End If
End If
For Each Ac_cell In Ac_Cells
For Each My_Pic In Sheet1.Shapes
If My_Pic.Left = Ac_cell.Offset(0, 1).Left And My_Pic.Top = Ac_cell.Offset(0, 1).Top Then
My_Pic.Delete
Exit For
End If
Next
St = Trim(Ac_cell.Value)
If Len(St) > 4 Then
If LCase(Left(St, 4)) = "http" Then
Call Insert_Picture(St, Ac_cell.Offset(0, 1))
GoTo Nextse1
End If
End If
myPath = "P:\"
If Right(myPath, 1) <> "\" Then myPath = myPath + "\"
If Not (Dir(myPath + St)) = "" Then
My_File = St
Else
My_File = Find_File(myPath, St)
End If
If My_File > " " Then
Call Insert_Picture(myPath + My_File, Ac_cell.Offset(0, 1))
End If
Application.ScreenUpdating = True
Nextse1:
Next
End Sub
Sub Insert_Picture(thePath As String, theRange As Range)
On Error GoTo Err3
Dim myPict As Shape
Sheet1.Shapes.AddPicture thePath, True, True, theRange.Left, theRange.Top, theRange.Width, theRange.Height
Set myPict = Sheet1.Shapes(Sheet1.Shapes.Count)
With myPict
.LockAspectRatio = msoFalse
.Placement = xlMoveAndSize
End With
Set myPict = Nothing
Exit Sub
Err3:
MsgBox Err.Description
End Sub
Function Find_File(thePath As String, F_N As String) As String
file = Dir(thePath)
Do Until file = ""
If Len(file) < Len(F_N) Then GoTo EXT_N1
If LCase(Left(file, Len(F_N))) = LCase(F_N) Then
Find_File = file
Exit Function
End If
EXT_N1:
file = Dir()
Loop
Find_File = ""
End Function
Put the EndsWith function in your code. (I included a starts with if it helps down the road) and use it like this:
If My_File > " " Then
If EndsWith(My_File,"-TH") Then
else
Call Insert_Picture(myPath + My_File, Ac_cell.Offset(0, 1))
End If
End If
Public Function EndsWith(str As String, ending As String) As Boolean
Dim endingLen As Integer
endingLen = Len(ending)
EndsWith = (Right(Trim(UCase(str)), endingLen) = UCase(ending))
End Function
Public Function StartsWith(str As String, start As String) As Boolean
Dim startLen As Integer
startLen = Len(start)
StartsWith = (Left(Trim(UCase(str)), startLen) = UCase(start))
End Function
Use InStr to search in the filename your pattern "-TH"
Dim pos As Integer
pos = InStr("find the comma, in the string", ",")

Capture Opened Workbook in New Instance of Excel

I have a number of macros where I want it run some code, then prompt the user to export an Excel workbook from another program, then run more code once the export has been opened. The tricky part is that some programs export to a new instance of Excel, while other programs export to the current instance.
The current workflow is (code at bottom):
Call the central 'Capture' Module with the name of the export (some
programs export 'Book[x]' some do 'workbook[x]', etc.) and the
procedure you want to run once the export is found.
Capture Module gets a list of all existing workbook names from all
Excel instances and saves as a Module-level string.
Capture Module uses Application.OnTime so that every 3 seconds, it
scans the list of all workbooks across all Excel instances.
If it finds a workbook that is not in the previously saved list of
all existing workbook names, and that contains the name of the
export, it stores that workbook as a public module level variable,
and runs the saved procedure from Step 1, which can the reference
the store workbook.
This works very well in all circumstances, EXCEPT for one. If I already have Book1.xlsx open in my current instance of Excel, and the 3rd party program exports Book1.xlsx to a NEW instance of Excel, the program doesn't recognize this as the export, since Book1.xlsx is in the existing workbook names string array already.
My solution is to find some way of uniquely identifying each workbook that's better than 'Name' or 'Path'. I tried saving each workbook name in the existing workbook names string as [application.hwnd]![workbook name] but this was an unstable fix and frequently broke (I don't really understand how hwnd works so I can't say why).
Any ideas? Thanks!
Sample Procedures That Use MCaptureExport
Public Sub GrabFXAllExport()
Const sSOURCE As String = "GrabFXAllExport"
On Error GoTo ErrorHandler
If Not TAAA.MCaptureExport.bCaptureExport("FXALL", "TAAA.FXAllEmail.ProcessFXAllExport") Then Err.Raise glHANDLED_ERROR
ErrorExit:
Exit Sub
ErrorHandler:
If bCentralErrorHandler(msMODULE, sSOURCE, , True) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Sub
Public Sub ProcessFXAllExport()
Const sSOURCE As String = "ProcessFXAllExport"
On Error GoTo ErrorHandler
If MCaptureExport.mwbCaptured Is Nothing Then
MsgBox "Exported Workbook Not Found. Please try again.", vbCritical, gsAPP_NAME
GoTo ErrorExit
End If
Dim wsSourceSheet As Worksheet
Set wsSourceSheet = MCaptureExport.mwbCaptured.Worksheets(1)
Set MCaptureExport.mwbCaptured = Nothing
[I now have the export and can work with it as a I please]
ErrorExit:
Exit Sub
ErrorHandler:
If bCentralErrorHandler(msMODULE, sSOURCE, , True) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Sub
MCaptureExport Module
Option Explicit
Option Base 1
' Description: This module contains the central error
' handler and related constant declarations.
Private Const msMODULE As String = "MCaptureExport"
Private sExistingWorkbookList() As String
Public mwbCaptured As Workbook
Public msCaptureType As String
Private sReturnProcedure As String
Private bListening As Boolean
Public Function bCaptureExport(sCaptureType As String, sRunAfterCapture As String) As Boolean
Dim bReturn As Boolean
Const sSOURCE As String = "bCaptureExport()"
On Error GoTo ErrorHandler
bReturn = True
If Not bWorkbookNamesAsArray(sExistingWorkbookList, True, False) Then Err.Raise glHANDLED_ERROR
sReturnProcedure = sRunAfterCapture
bListening = True
msCaptureType = sCaptureType
TAAA.MCaptureExport.WaitForCapture sCaptureTypeToNameContains(msCaptureType)
MsgBox "Waiting for " & msCaptureType & " Export", vbInformation, gsAPP_NAME
ErrorExit:
bCaptureExport = bReturn
Exit Function
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msMODULE, sSOURCE) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function
Private Sub WaitForCapture(sNameContains As String)
Const sSOURCE As String = "WaitForCapture"
On Error GoTo ErrorHandler
Dim wbCaptureCheck As Workbook
If Not bCaptureCheck(sNameContains, wbCaptureCheck) Then Err.Raise glHANDLED_ERROR
If wbCaptureCheck Is Nothing Then
If bListening Then _
Application.OnTime Now + TimeSerial(0, 0, 3), "'TAAA.MCaptureExport.WaitForCapture " & Chr(34) & sNameContains & Chr(34) & "'"
Else
Dim bSameApp As Boolean
If Not bWorkbooksInSameApp(ThisWorkbook, wbCaptureCheck, bSameApp) Then Err.Raise glHANDLED_ERROR
If Not bSameApp Then
Dim sTempFilePath As String
sTempFilePath = ThisWorkbook.Path & "\temp_" & Format(Now, "mmddyyhhmmss") & ".xls"
wbCaptureCheck.SaveCopyAs sTempFilePath
wbCaptureCheck.Close SaveChanges:=False
Set wbCaptureCheck = Application.Workbooks.Open(sTempFilePath)
End If
Set mwbCaptured = wbCaptureCheck
bListening = False
Application.Run sReturnProcedure
End If
ErrorExit:
Exit Sub
ErrorHandler:
If bCentralErrorHandler(msMODULE, sSOURCE, , True) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Sub
Private Function sCaptureTypeToNameContains(sCaptureType As String) As String
sCaptureTypeToNameContains = "*"
On Error Resume Next
Select Case UCase(sCaptureType)
Case "SOTER": sCaptureTypeToNameContains = "workbook"
Case "THOR": sCaptureTypeToNameContains = "Book"
Case "FXALL": sCaptureTypeToNameContains = "search_results_export"
End Select
End Function
Private Function bCaptureCheck(sNameContains As String, wbResult As Workbook) As Boolean
Dim bReturn As Boolean
Const sSOURCE As String = "bCaptureCheck()"
On Error GoTo ErrorHandler
bReturn = True
Dim i As Long, wb As Workbook
Dim xlApps() As Application
If Not bGetAllExcelInstances(xlApps) Then Err.Raise glHANDLED_ERROR
For i = LBound(xlApps) To UBound(xlApps)
For Each wb In xlApps(i).Workbooks
If wb.Name Like "*" & sNameContains & "*" _
And Not bIsInArray(wb.Name, sExistingWorkbookList) Then
Set wbResult = wb
GoTo ErrorExit
End If
Next
Next
ErrorExit:
bCaptureCheck = bReturn
Exit Function
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msMODULE, sSOURCE) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function
Utility Functions Used by MCaptureExport
Public Function bWorkbookNamesAsArray(sResult() As String, Optional bAllInstances As Boolean = True) As Boolean
Dim bReturn As Boolean
Const sSOURCE As String = "bWorkbookNamesAsArray()"
On Error GoTo ErrorHandler
bReturn = True
Dim i As Long, wb As Workbook
Dim xlApps() As Application
Dim ResultArray() As String
Dim Ndx As Integer, wbCount As Integer
If bAllInstances Then
If Not bGetAllExcelInstances(xlApps) Then Err.Raise glHANDLED_ERROR
Else
ReDim xlApps(0)
Set xlApps(0) = Application
End If
For i = LBound(xlApps) To UBound(xlApps)
For Each wb In xlApps(i).Workbooks
wbCount = wbCount + 1
Next
Next
ReDim ResultArray(1 To wbCount)
For i = LBound(xlApps) To UBound(xlApps)
For Each wb In xlApps(i).Workbooks
Ndx = Ndx + 1
ResultArray(Ndx) = wb.Name
Next
Next
sResult = ResultArray()
ErrorExit:
bWorkbookNamesAsArray = bReturn
Exit Function
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msMODULE, sSOURCE) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function
Public Function bGetAllExcelInstances(xlApps() As Application) As Boolean
Dim bReturn As Boolean
Const sSOURCE As String = "bGetAllExcelInstances()"
On Error GoTo ErrorHandler
bReturn = True
Dim n As Long
Dim hWndMain As LongPtr
Dim app As Application
' Cater for 100 potential Excel instances, clearly could be better
ReDim xlApps(1 To 100)
hWndMain = FindWindowEx(0&, 0&, "XLMAIN", vbNullString)
Do While hWndMain <> 0
If Not bGetExcelObjectFromHwnd(hWndMain, app) Then Err.Raise glHANDLED_ERROR
If Not (app Is Nothing) Then
If n = 0 Then
n = n + 1
Set xlApps(n) = app
ElseIf bCheckHwnds(xlApps, app.Hwnd) Then
n = n + 1
Set xlApps(n) = app
End If
End If
hWndMain = FindWindowEx(0&, hWndMain, "XLMAIN", vbNullString)
Loop
If n Then
ReDim Preserve xlApps(1 To n)
'GetAllExcelInstances = n
Else
Erase xlApps
End If
ErrorExit:
bGetAllExcelInstances = bReturn
Exit Function
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msMODULE, sSOURCE) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function
Private Function bCheckHwnds(xlApps() As Application, Hwnd As LongPtr) As Boolean
On Error Resume Next
Dim i As Integer
For i = LBound(xlApps) To UBound(xlApps)
If Not xlApps(i) Is Nothing Then
If xlApps(i).Hwnd = Hwnd Then
bCheckHwnds = False
Exit Function
End If
End If
Next i
bCheckHwnds = True
End Function
Public Function bWorkbooksInSameApp(wb1 As Workbook, wb2 As Workbook, bSameApp As Boolean) As Boolean
Dim bReturn As Boolean
Const sSOURCE As String = "bWorkbooksInSameApp()"
On Error GoTo ErrorHandler
bReturn = True
bSameApp = wb1.Application.Hwnd = wb2.Application.Hwnd
ErrorExit:
bWorkbooksInSameApp = bReturn
Exit Function
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msMODULE, sSOURCE) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function
Private Function bGetExcelObjectFromHwnd(ByVal hWndMain As LongPtr, aAppResult As Application) As Boolean
Dim bReturn As Boolean
Const sSOURCE As String = "bGetExcelObjectFromHwnd()"
On Error GoTo ErrorHandler
bReturn = True
Dim hWndDesk As LongPtr
Dim Hwnd As LongPtr
Dim strText As String
Dim lngRet As Long
Dim iid As UUID
Dim obj As Object
hWndDesk = FindWindowEx(hWndMain, 0&, "XLDESK", vbNullString)
If hWndDesk <> 0 Then
Hwnd = FindWindowEx(hWndDesk, 0, vbNullString, vbNullString)
Do While Hwnd <> 0
strText = String$(100, Chr$(0))
lngRet = CLng(GetClassName(Hwnd, strText, 100))
If Left$(strText, lngRet) = "EXCEL7" Then
Call IIDFromString(StrPtr(IID_IDispatch), iid)
If AccessibleObjectFromWindow(Hwnd, OBJID_NATIVEOM, iid, obj) = 0 Then 'S_OK
Set aAppResult = obj.Application
GoTo ErrorExit
End If
End If
Hwnd = FindWindowEx(hWndDesk, Hwnd, vbNullString, vbNullString)
Loop
End If
ErrorExit:
bGetExcelObjectFromHwnd = bReturn
Exit Function
ErrorHandler:
MsgBox Err.Number
bReturn = False
If bCentralErrorHandler(msMODULE, sSOURCE) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function
I have a potential solution. However I want to leave the question open. This is a fairly complicated problem and I bet there are more elegant solutions than what I'm proposing.
So I updated the format of sExistingWorkbookList to [Application.hWnd]![Workbook.name]. I had tried this before but I think it's working this time.
Thoughts?
Updated Version of bWorkbookNamesAsArray
Added wb.Application.Hwnd & "!" & to ResultArray(Ndx) = wb.name
Public Function bWorkbookNamesAsArray(sResult() As String, Optional bAllInstances As Boolean = True) As Boolean
Dim bReturn As Boolean
Const sSOURCE As String = "bWorkbookNamesAsArray()"
On Error GoTo ErrorHandler
bReturn = True
Dim i As Long, wb As Workbook
Dim xlApps() As Application
Dim ResultArray() As String
Dim Ndx As Integer, wbCount As Integer
If bAllInstances Then
If Not bGetAllExcelInstances(xlApps) Then Err.Raise glHANDLED_ERROR
Else
ReDim xlApps(0)
Set xlApps(0) = Application
End If
For i = LBound(xlApps) To UBound(xlApps)
For Each wb In xlApps(i).Workbooks
wbCount = wbCount + 1
Next
Next
ReDim ResultArray(1 To wbCount)
For i = LBound(xlApps) To UBound(xlApps)
For Each wb In xlApps(i).Workbooks
Ndx = Ndx + 1
ResultArray(Ndx) = wb.Application.Hwnd & "!" & wb.Name
Next
Next
sResult = ResultArray()
ErrorExit:
bWorkbookNamesAsArray = bReturn
Exit Function
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msMODULE, sSOURCE) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function
New Utility Function
Public Function bGetWorkbookFromHwndAndName(ByVal sWorkbookReference As String, ByRef wbResult As Workbook)
Dim bReturn As Boolean
Const sSOURCE As String = "bGetWorkbookFromHwndAndName()"
On Error GoTo ErrorHandler
bReturn = True
Dim xlApp As Application
If Not bGetExcelObjectFromHwnd(CLng(Split(sWorkbookReference, "!")(0)), xlApp) Then Err.Raise glHANDLED_ERROR
Set wbResult = xlApp.Workbooks(Split(sWorkbookReference, "!")(1))
ErrorExit:
bGetWorkbookFromHwndAndName = bReturn
Exit Function
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msMODULE, sSOURCE) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function
Updated MCaptureExport.bCaptureCheck()
Private Function bCaptureCheck(sNameContains As String, wbResult As Workbook) As Boolean
Dim bReturn As Boolean
Const sSOURCE As String = "bCaptureCheck()"
On Error GoTo ErrorHandler
bReturn = True
Dim i As Long, wb As Workbook, sFullWorkbookReference As String
Dim xlApps() As Application
If Not bGetAllExcelInstances(xlApps) Then Err.Raise glHANDLED_ERROR
For i = LBound(xlApps) To UBound(xlApps)
For Each wb In xlApps(i).Workbooks
sFullWorkbookReference = wb.Application.Hwnd & "!" & wb.Name
If wb.Name Like "*" & sNameContains & "*" _
And Not bIsInArray(sFullWorkbookReference, sExistingWorkbookList) Then
If Not bGetWorkbookFromHwndAndName(sFullWorkbookReference, wbResult) Then Err.Raise glHANDLED_ERROR
GoTo ErrorExit
End If
Next
Next
ErrorExit:
bCaptureCheck = bReturn
Exit Function
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msMODULE, sSOURCE) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function

Interact with Active/Single IE11 Session VBA

So I have a loop that exports data from a website. However, for each case, it starts a new session and closes. Is there a method to navigate and download for all the cases in just one IE11 session and then closing out? Below is the code that I have right now:
Public Sub Get_File()
Dim sFiletype As String 'Fund type reference
Dim sFilename As String 'File name (fund type + date of download), if "" then default
Dim sFolder As String 'Folder name (fund type), if "" then default
Dim bReplace As Boolean 'To replace the existing file or not
Dim sURL As String 'The URL to the location to extract information
Dim Cell, Rng As Range
Dim Sheet As Worksheet
'Initialize variables
Set Rng = Range("I2:I15")
Set Sheet = ActiveWorkbook.Sheets("Macro_Button")
For Each Cell In Rng
If Cell <> "" Then
sFiletype = Cell.Value
sFilename = sFiletype & "_" & Format(Date, "mmddyyyy")
sFolder = Application.WorksheetFunction.VLookup(Cell.Value, Sheet.Range("I2:J15"), 2, False)
bReplace = True
sURL = "www.preqin.com"
'Download using the desired approach, XMLHTTP / IE
If Application.WorksheetFunction.VLookup(Cell.Value, Sheet.Range("I2:W15"), 15, False) = 1 Then
Call Download_Use_IE(sURL, sFilename, sFolder, bReplace)
Else
Call Download_NoLogin_Use_IE(sURL, sFilename, sFolder, bReplace)
End If
Else
Exit Sub
End If
Next
End Sub
Private Sub Download_Use_IE(ByRef sURL As String, _
Optional ByRef sFilename As String = "", _
Optional ByRef sFolder As String = "", _
Optional ByRef bReplace As Boolean = True)
Dim oBrowser As InternetExplorer
Dim hDoc As HTMLDocument
Dim objInputs As Object
Dim ele As Object
On Error GoTo ErrorHandler
'Create IE object
Set oBrowser = New InternetExplorer
oBrowser.Visible = True
'Navigate to URL
Call oBrowser.navigate(sURL)
While oBrowser.Busy Or oBrowser.readyState <> 4: DoEvents: Wend
'Skips log in step if already signed into website
On Error GoTo LoggedIn
'Enter username
oBrowser.document.getElementById("ctl00_ctl00_cphSiteHeader_ucLoginForm_user_email").Value = "XXX"
oBrowser.document.getElementById("ctl00_ctl00_cphSiteHeader_ucLoginForm_user_password").Value = "XXX"
'Submit the sign in
oBrowser.document.getElementById("ctl00_ctl00_cphSiteHeader_ucLoginForm_btnLogin").Click
'Wait for website to load
While oBrowser.Busy Or oBrowser.readyState <> 4: DoEvents: Wend
LoggedIn:
'All PE
oBrowser.navigate Range("H3").Value
'Wait for website to load
While oBrowser.Busy Or oBrowser.readyState <> 4: DoEvents: Wend
'Set the htmldocument
Set hDoc = oBrowser.document
'Loop and click the download file button
Set objInputs = oBrowser.document.getElementsbyTagName("input")
For Each ele In objInputs
If ele.Title Like "Download Data to Excel" Then
ele.Click
End If
Next
'Wait for dialogue box to load
While oBrowser.Busy Or oBrowser.readyState > 3: DoEvents: Wend
Application.Wait (Now + TimeValue("0:00:02"))
'IE 9+ requires to confirm save
Call Download(oBrowser, sFilename, sFolder, bReplace)
'Close IE
oBrowser.Quit
Exit Sub
ErrorHandler:
'Resume
Debug.Print "Sub Download_Use_IE() " & Err & ": " & Error(Err)
End Sub
Modify your download_IE procedure to use a Browser that is passed to it:
Private Sub Download_Use_IE(oBrowser As InternetExplorer, _
ByRef sURL As String, _
Optional ByRef sFilename As String = "", _
Optional ByRef sFolder As String = "", _
Optional ByRef bReplace As Boolean = True)
Dim hDoc As HTMLDocument
Dim objInputs As Object
Dim ele As Object
On Error GoTo ErrorHandler
'Create IE object
oBrowser.Visible = True
'Navigate to URL
Call oBrowser.navigate(sURL)
......rest of code
Call Download(oBrowser, sFilename, sFolder, bReplace)
'Do not Close IE
Exit Sub
ErrorHandler:
'Resume
Debug.Print "Sub Download_Use_IE() " & Err & ": " & Error(Err)
End Sub
Then modify your procedure to pass this object:
Public Sub Get_File()
'declare all variables plus:
Dim oBrowser As InternetExplorer
Set oBrowser = New InternetExplorer
.....put additional code here.....
If Application.WorksheetFunction.VLookup(Cell.Value, Sheet.Range("I2:W15"), 15, False) = 1 Then
Call Download_Use_IE(oBrowser, sURL, sFilename, sFolder, bReplace)
Else
Call Download_NoLogin_Use_IE(oBrowser, sURL, sFilename, sFolder, bReplace)
End If
Else
Exit Sub
End If
Next
'Close IE
oBrowser.Quit
End Sub
You will need to do the same thing for the other procedure.