I have created an Excel workbook that uses Userforms as a Menu for users, meaning that they don't interact with the workbook itself. Usually, this is opened by double click with works perfectly.
Now, I would like to open this file from another excel file and to prevent users from being locked into the target workbook (since the userform is modal), I am opening it in a new instance.
I have looked into various methods (among them https://www.mrexcel.com/forum/excel-questions/570562-vba-open-another-instant-excel-run-macro.html), where I found code that seems to be working and also starts the Auto_Open-code (previous attempts opened the file but did not start the code) BUT since the target file has application-closing-code integrated (due to also being opened via double click), it gives me a runtime error (440) upon closing the target workbook since I believe that the Application.Quit-command is interrupting the Run-command and causing the error.
I also found someone with a similar problem (between access and excel but I believe its basically the same thing for this case) at https://access-programmers.co.uk/forums/showthread.php?t=153101 but the suggestion of putting the closing code into the calling file does not work for me, since the target file gets opened independent from the calling file half the time.
Is there any way around this or maybe another method to open the target workbook from another excel workbook and also run the code after opening?
Also, if that is an option, maybe there is a way to always open the target file (no matter the source) in a new instance that does not involve administrator rights at all (because I don't have any)?
The relevant code pieces are as follows:
Source file (opening the target file):
Dim aExcel As Application
Dim wbTarget As Workbook
Dim sReturn As String
Dim sArg As String
Dim sFile As String
' File to be opened
sFile = "C:\file.xlsm"
' Neue Instance
Set aExcel = New Application
With aExcel
.Visible = True
' WB-Reference for opening of WB
Set wbTarget = aExcel.Workbooks.Open(sFile)
sArg = "'" & wbTarget.Name & "'!ModPublic.Auto_Open"
' Run Code within Targetfile (bc code does not start otherwise)
aExcel.Run sArg
' Not reached due to runtime error:
'aExcel.Quit
End With
Target File (gets open via auto_open and does its thing - afterwards gets closed via [x] on the userform):
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
' [x] is clicked (CloseMode = 0)
If CloseMode <> 1 Then
' deactivate Cancel
Cancel = 1
' 1 wb is open, save file and quit application
If Application.Workbooks.Count = 1 Then
ActiveWorkbook.Saved = True
Application.Quit
' more than 1 is open, save file and close wb
Else
ActiveWorkbook.Close SaveChanges:=True
End If
End If
End Sub
Maybe a bit of a workaround, but can you create a global variable in the target workbook, which specifies that the workbook is actually opened from the source workbook? And then use the global variable in the QueryClose sub with an if statement around Application.Quit?
Related
I have a 3 master sheets that I frequently want open (May, June, and July). I have multiple other macros that grab data from these master sheets.
I've created a macro (OpenFiles) that opens them. I want to be able to call OpenFiles at the start of a macro if I know I will be referencing one of these master sheets.
Sub OpenFiles calls another sub(zzGetIt) that checks if the workbook is already open, otherwise it opens it. It works perfectly if I run it from the VBA application, or by choosing it in the macro list in Excel. But if I use a hotkey to call it, or if it is called through another macro, it exits out of all subs after opening a single file (and ends on that workbook instead of activating my original active workbook).
It will open a workbook if one of them is missing, but it will never open more than one (if, say I have only 1/3 workbooks open- 2 should open). And the only scenario where the macro will continue to the msgbox at the end is if all three files are already open.
Please help- I think this must be super obvious since the macro runs fine if I run it from VBA.
I've tried the following:
Removed any error handling that could be hiding a problem with the
sub/function
Set Tools>Options>"Break on all Errors" and I still don't receive any
errors when the sub ends early.
Went through the whole sub with F8- it runs perfectly when I use that.
Call OpenFiles multiple times in a macro but the sub ends after the sub is called the first time so the rest never even run.
Sub zzGetIt(sfullname As String)
Dim ZGetIt As Workbook
Dim wb As Workbook
Dim ReadOnly As Boolean
Dim o As Boolean
Dim sFile As String
sFile = Dir(sfullname)
MsgBox ("Trying to fetch")
For Each wb In Application.Workbooks
If wb.Name = sFile Then
o = True
Exit For
End If
Next wb
If o = False Then
Set zGetIt = Workbooks.Open(sfullname, ReadOnly:=ReadOnly)
End If
'reset o
o = False
MsgBox ("Finished fetching " & sFile)
End Sub
Sub OpenFiles()
Dim Current As Worksheet
Set Current = ActiveSheet
Dim May As String
Dim Jun As String
Dim Jul As String
May = "A:\Files\My Stuff\05 May 2019 - Master.xlsx"
Jun = "A:\Files\My Stuff\06 June 2019 - Master.xlsx"
Jul = "A:\Files\My Stuff\07 July 2019 - Master.xlsx"
Call zzGetIt(May)
Call zzGetIt(Jun)
Call zzGetIt(Jul)
Current.Activate
Set Current = Nothing
Msgbox("I can only get this msgbox if I run from macro list or
VBA application OR if all 3 workbooks were already open before I ran the
macro")
End Sub
If May needs to be opened it will stop at May so I do not receive the msgbox after the sub is called for the first time.
I want the macro to open any of the three workbooks that are not already open and I need it to continue until the msgbox at the very end pops up
I don't see anything obviously wrong with your code that might cause the observed behavior. But I would still do it differently. Perhaps this will help. I've revised your procedures that check for the file already open/open the file if not already open, but apart from that the main difference is that I'm calling this procedure in a loop from OpenFiles.
Option Explicit
Sub OpenFiles()
Dim Current As Worksheet
Set Current = ActiveSheet
Dim files As New Collection
Dim file
files.Add "A:\Files\My Stuff\05 May 2019 - Master.xlsx"
files.Add "A:\Files\My Stuff\06 June 2019 - Master.xlsx"
files.Add "A:\Files\My Stuff\07 July 2019 - Master.xlsx"
For Each file In files
Debug.Print "Fetching file " & file
If isFileOpen(CStr(file)) Then
Debug.Print file & " is already open :) "
Else
Call GetFile(CStr(file), False)
End If
Next
Current.Activate
Set Current = Nothing
MsgBox ("Finished!")
End Sub
Private Function isFileOpen(fullPath$) As Boolean
Dim wb As Workbook
On Error Resume Next
Set wb = Workbooks(Dir(fullPath))
If Err.Number = 0 Then isFileOpen = True
End Function
Private Sub GetFile(fullPath$, readOnly As Boolean)
' No error handling here, this SHOULD raise an error if the file can't
' be opened for any reason (invalid path, locked/in-use unless readOnly=True, etc.
Debug.Print "Attempting to open " & fullPath
Workbooks.Open fullPath, readOnly:=readOnly
End Sub
I've solved the issue... found this article. The issue is using a hotkey with SHIFT. Hotkey used must be lower-case or use a button to call the macro
Ctrl+Shift+a
'won't work
Ctrl+a
'will work
(Minimum requirements: Excel 2010 and Windows 7)
I have managed to use Bill Manville’s answer found in MSDN with minor changes. The suggested recursive code basically uses files’s Workbook_Open to create a separate instance and taht instance opens the file as editable with no prompts for read-only access.
Private Sub Workbook_Open()
Dim oXcl As Excel.Application
If Workbooks.Count > 1 Then
ThisWorkbook.Saved = True
ThisWorkbook.ChangeFileAccess xlReadOnly
Set oXcl = CreateObject("Excel.Application")
oXcl.ScreenUpdating = False
oXcl.Visible = True
oXcl.Workbooks.Open fileName:=ThisWorkbook.FullName, ReadOnly:=False
AppActivate oXcl.Caption
ThisWorkbook.Close SaveChanges:=False
Else
Call Continue_Open
End If
End Sub
The code works very well when Excel is already running as it creates a new instance of Excel and if a new Excel file is opened, it goes to a different Excel instance (running prior to it). But if the file with the Workbook_Open is the one that starts Excel, any further Excel files opened by double-clicking open within that Excel instance as it is the earliest run instance thus ceasing to be separate.
I have got as far as to be able to tell (Windows) whether that file starts Excel by using
Function NumberOfExcelInstances()
strComputer = "."
Set objWMI = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set proc = objWMI.ExecQuery("Select * from Win32_Process Where Name = 'Excel.exe'")
NumberOfExcelInstances = proc.Count
End Function
But I have not been able to find a way to tell NOT to use that Excel instance when opening new files. Any code should be bundled inside the Excel file with the Worbook_Open code.
How could I possibly include VBA code inside a file so that it opens in a separate Excel instance even when that file is the one that fires Excel?
After research on code at Application level, a working solution have been found. I am posting it, in case it is of interest to someone else.
When workbook opens the fist time it sets a workbook open event subroutine at Application level (rather than at Workbook level).
When a new workbook opens, the sub at Applictaion level opens a new instance with the workbook to be kept separate by recursivity - closes that workbook in the application instance that checks being separate thus removing the event handler from the application instance and sets that event handler and code on the newly created application instance.
All relevant code is included and it needs to be in three different modules.
1-a VBA Class Module named cXlEvents is created with the following code:
'VBA Class Module named cXlEvents
Public WithEvents appWithEvents As Application
'Instance variables
Dim sEventSetterPath As String
Dim sEventSetterName As String
Private Sub appWithEvents_WorkbookOpen(ByVal Wb As Workbook)
Call NewAppInstance(Wb, sEventSetterPath, sEventSetterName)
End Sub
2-ThisWorkbook Module includes:
'1-ThisWorkbook VBA Module calling events at
'Workbook level.
'2-At Workbook Open set Application level event
'handler and then instance code by calling subs
'held in VBA standard module.
Private Sub Workbook_Open()
Call SetEventHandler
Call NewAppInstance(Me)
End Sub
'Code to call "undo" special settings upon opening
'when file closes
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Call UndoSettings
End Sub
3-All code necessary to create an instance at Workbook level Open event from a class which will end up at Application level is in a standard VBA Module:
'In a VBA standard Module
Dim oXlEvents As New cXlEvents
Sub SetEventHandler()
If oXlEvents.appWithEvents Is Nothing Then
Set oXlEvents.appWithEvents = Application
End If
End Sub
Sub NewAppInstance(wbWbook As Workbook, Optional sEventSetterPath As String, Optional sEventSetterName As String)
Dim oXcl As Excel.Application
Dim wbEventSet As Workbook
Dim lCaseNum As Long
Dim sResetMacro As String: sResetMacroName = "UndoSettings"
'Set instance variables
sEventSetterPath = ThisWorkbook.FullName
sEventSetterName = ThisWorkbook.Name
If wbWbook.ReadOnly And wbWbook.FullName = sEventSetterPath Then
MsgBox "Already open - please use open file.", , "WARNING"
wbWbook.Close False
Exit Sub
End If
If Workbooks.Count > 1 Then
If wbWbook.FullName <> sEventSetterPath Then
lCaseNum = 1
Set wbEventSet = Workbooks(1)
wbEventSet.Save
Application.Run "'" & sEventSetterName & "'!'" & sResetMacro & "'"
Else
lCaseNum = 2
Set wbEventSet = wbWbook
wbEventSet.Saved = True
End If
wbEventSet.ChangeFileAccess xlReadOnly
Set oXcl = CreateObject("Excel.Application")
oXcl.Workbooks.Open Filename:=sEventSetterPath, ReadOnly:=False
oXcl.Visible = True
Set oXlEvents.appWithEvents = Nothing
Select Case lCaseNum
Case Is = 1
AppActivate Application.Caption
Case Is = 2
AppActivate oXcl.Caption
End Select
wbEventSet.Close False
Else
Call Continue_Open
End If
End Sub
Sub Continue_Open()
'Code with special settings and procedures required for the workbook
End Sub
Sub UndoSettings()
'Code to "undo" any special settings when workbook opened
End Sub
I have code in workbook A that opens and does stuff to workbook B. Code runs fine when workbook A and B are the only excel files open (or if workbook A is the only file open). However, if I open up any additional workbook (call it workbook C), the macro does not run correctly. It doesn't cause an error message, it just runs to completion without doing any of the "stuff" it's supposed to do (the stuff is basically finding things in workbook B and pasting them into workbook A).
FWIW, I have done the following simple experiment:
Open all 3 workbooks (A, B, & C)
select workbook C so that it is active and the front window
run the code workbookB.sheet1.activate (this is not verbatim, I know this code as written will fail)
When I do the above test, it doesn't even make workbook B the active workbook. Again, it doesn't cause excel to throw an error message, it just runs and leave workbook C as the active workbook.
Edit: I've tested more, the code below should change the value of a cell in workbook B, but instead is putting the value into workbook C. I am very confused, since workbook C is not referenced in any way (the module is in workbook A)
Sub test()
Dim wb As Workbook
Set wb = Workbooks.Open("U:\workbookB.xlsx")
wb.Worksheets("ED").Range("Z1").Value = "TEST"
End Sub
Edit 2: The issue was occurring when Workbook A and B had been open for several hours, and Workbook C was recently opened. I closed workbook B then re-ran the code and it is working correctly. This leading me to believe that there IS some sort of issue with multiple instances of excel opening. While this is hopefully low-risk, I am still curious if anyone has some way I could code around it as a precaution? Thanks!
There is a subtle bug (I hesitate to call it that, but that's what it looks like) you need to watch out for.
If the workbook you're trying to open via code is already open then occasionally you will see some unexpected behavior (such as the return value from Workbooks.Open() being assigned to ThisWorkbook instead of the file you expect).
For example the code below runs in "Tester.xlsm" and is opening "EmptyTest.xlsx", but if that file is already open, the Workbooks.Open call fails to correctly assign the wb variable, and it ends up pointing at "Tester.xlsm". That can cause problems.
To replicate,
open Excel
open "EmptyTest.xlsx"
open "Tester.xlsm"
run "Tester" sub
Test code:
Sub Tester()
Dim wb As Workbook
'with "EmptyTest" already open
Set wb = Workbooks.Open("C:\Tester\EmptyTest.xlsx")
Debug.Print wb.Name '>> Tester.xlsm - oops!
'close"EmptyTest" before proceeding
Workbooks("EmptyTest.xlsx").Close False
'with "EmptyTest" closed
Set wb = Workbooks.Open("C:\Tester\EmptyTest.xlsx")
Debug.Print wb.Name '>>EmptyTest.xlsx - OK
End Sub
Totally reproducible on my system (win10/Office 365)
I think your problem is that you are opening Workbook B. You state, if you close and re-open it then it works.
Therefore, any changes you have made to Workbook B will be lost as it will re-open the original workbook. (Auto Save does not actually save the workbook, it saves a separate copy.)
You need instead to check if the workbook is already open and only re-open it if it is not currently open.
(Very rough code)
Sub test()
Dim wb As Workbook
For Each wb In ThisWorkbook.Application.Workbooks
If wb.Path & "\" & wb.Name = "U:\workbookB.xlsx" Then Exit For
Next
If wb.Path & "\" & wb.Name <> "U:\workbookB.xlsx" Then Set wb = ThisWorkbook.Application.Workbooks.Open("U:\workbookB.xlsx")
wb.Worksheets("ED").Range("Z1").Value = "TEST"
End Sub
But (as others have said) you should really check that there are no other instances of Excel running and account for them also.
I have an excel workbook that acts as a Launchpad for other excel based tools allow the user to launch multiple instances of the same tool I am forcing the new tools to open in a new excel instance each time by setting a new application. The code then also passes some data from the launchpad into the tools as well.
Unfortunately for some users, currently only a handful from some 20-30 that have used the system, if they close the launchpad workbook it seems to persist in that instance of excel (It remains visible in the VBE project explorer). What's stranger is that whatever they now do in any workbook that remains opened in that instance of excel e.g.type into a cell and press return, the launchpad is re-opened. They have to completely shut down excel to stop the issue.
I release all the objects used in the routine that opens the tools so it can not be this, I am wondering if this is necessary given I am opening the workbooks and wanting to leave them open for the user?
My Code:
Public Sub LaunchTool()
Dim WB1 As Workbook
Dim WS1 As Worksheet
Dim WT1 As Workbook
Dim EA1 As New Application
Application.DisplayAlerts = False
'Define workbook aliases
Set WT1 = ActiveWorkbook
EA1.Workbooks.Open Filename:=PathString, IgnoreReadOnlyRecommended:=True,
ReadOnly:=True
Set WB1 = EA1.ActiveWorkbook
Set WS1 = WB1.Worksheets("Import")
WB1.Windows(1).Visible = True
EA1.Visible = True
'pass on some values
WB1.Sheets("Control").Range("Dev_Flag") =
WT1.Sheets("Param").Range("Dev_Flag")
Set WT1 = Nothing
Set WS1 = Nothing
Set WB1 = Nothing
Set EA1 = Nothing
Application.DisplayAlerts = True
End Sub
The Application.DisplayAlerts = False is to suppress the Microsoft Excel is waiting for another application to complete an OLE action alert that sometimes pops up during the loading.
I have tried the following in the ThisWorkbook module:
Private WithEvents XL As Excel.Application
Private Sub XL_WorkbookBeforeClose(ByVal Wb As Workbook, Cancel As Boolean)
Set XL = Nothing
Workbooks.Open ThisWorkbook.FullName
ThisWorkbook.Close False
End Sub
But no luck!
For most users, everything works fine but some users as I described cannot successfully close down the launchpad and then unless they close excel completely or at least the instance that the launchpad was open in then any user action begins to trigger the launchpad to reopen.
Thanks.
After much deliberation, I realised that the issue was not being driven by the objects but rather that I had an Application.OnKey trigger that was being set but never released and this was causing the workbook to persist, releasing the OnKey trigger in the WorkbookBeforeClose routine has solved it nicely.
Thank you for your help.
I am trying to schedule a task in Window's Task Scheduler that opens an excel file and auto run the macro in it.
The macro part is all done, but one problem I have is when the file is already open and the scheduled task runs. Whenever this happens, it will ask you if you want to reopen the file, if i accidentally click yes, which actually happened, then the data I was working on will all disappear.
So what I want is for the task to not run when file is open. Do I need to run a script? create a batch file with the code? If possible can you provide me a code that can do this as I am not familiar with another language aside from VBA
Note.
Just to clarify, I have a file that constantly pull data form Live Bloomberg. What I want to do is to save it as another workbook for reference everyday at 4:30pm. There is a few problem.
Here is how what I wrote in ThisWorkbook for easier understanding what I want to accomplish
Private Sub Workbook_Open()
Application.OnTime TimeValue("16:30:00"), "MyMacro"
End Sub
1)If I didn't open that excel that day, Workbook_Open() simply won't run.
2)Even if I did open the excel, if I close Excel before 4:30pm the macro won't run
So what I want is to have windows task scheduler constantly open the excel file for me that will always run on time regardless of what happens.
There are other problems too. I always write comments and stuff to the Live Data File, so if Task Scheduler tries to open the data file which in turn activating the Workbook_Open() while I am using it, a popup will come up asking if I want to reopen file. Of course I simply need to press "no" then the task will stop and everything is fine. But what if I wasn't there to press the button? Also on a few occasion I accidentally press "yes", which wiped out all the comments I wrote.
Therefore what I wanted to ask is that if The Live Data File is already open, simply make it so that the task in Task Scheduler won't run.
Have you considered using the Workbook.ReadOnly Property? This won't exactly tell you if the file is open but it will tell you if you have write-access. Try:
Public Sub OnlyRunWithWriteAccess()
Dim oBook As Workbook
Set oBook = Application.ActiveWorkbook
If Not oBook.ReadOnly Then
'Your processing code goes here...
End If
Set oBook = Nothing
End Sub
First of all thanks Steve S for your input.
I already did a workaround. Instead of having the LiveData file to run the macro, I put the macro on a clean excel template and have task scheduler open that file instead. This way is much more reliable and skips all the "Scripting in task scheduler" thing.
Here is my updated code:
'In ThisWorkbook
Public Sub Workbook_Open()
Run "AutoSaveAs"
End Sub
'In Module1
Public Sub AutoSaveAs()
Application.ScreenUpdating = False
Dim LDSP As String
Dim IsOTF
Dim LDS, TWB As Workbook
'Set LiveDealSheet file path
'Check if LiveDealSheet is already open
LDSP = "I:\LiveDealSheet\LiveDealSheet.xlsm"
IsOTF = IsWorkBookOpen(LDSP)
'Set quick workbook shortcut
Set TWB = ThisWorkbook
If IsOTF = False Then
Set LDS = Workbooks.Open(LDSP)
Else
Workbooks("LiveDealSheet.xlsm").Activate
Set LDS = ActiveWorkbook
End If
'Copy all data from LIVE Deals to TWB
LDS.Sheets("LIVE Deals").Cells.Select
Selection.Copy
TWB.Sheets(1).Cells.PasteSpecial (xlValues)
TWB.Sheets(1).Cells.PasteSpecial (xlFormats)
TWB.SaveAs FileName:="I:\LiveDealSheet\" & Format(Date, "yyyymmdd") & " LiveDealSheet", FileFormat:=52
If IsOTF = False Then
LDS.Close False
TWB.Close False
Else
TWB.Close False
End If
Application.ScreenUpdating = True
End Sub
Function IsWorkBookOpen(FileName As String)
Dim ff As Long, ErrNo As Long
On Error Resume Next
ff = FreeFile()
Open FileName For Input Lock Read As #ff
Close ff
ErrNo = Err
On Error GoTo 0
Select Case ErrNo
Case 0: IsWorkBookOpen = False
Case 70: IsWorkBookOpen = True
Case Else: Error ErrNo
End Select
End Function