stop task from running when file is open - excel

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

Related

Application.Quit closes all open Excel files

I want to close the active macro workbook inside the Userform_Terminate event. The problem I am facing is that, a ghost window of excel application lingers on even after workbook has been closed.
Have tried most of the suggested ways, I could get my hands on (described in detail in the code snippet) but to no avail. If anybody can help, much grateful.
NOTE: Have released almost all excel related objects from memory by setting it to nothing.
Code :
Private Sub UserForm_Terminate()
' Application.DisplayAlerts = False ' The excel ghost window lingers on
' ThisWorkbook.Close , False
' Application.DisplayAlerts = True
'
' Application.DisplayAlerts = False ' The excel ghost window lingers on
' ThisWorkbook.Saved = True
' ThisWorkbook.Close , False
' Application.DisplayAlerts = True
' Application.DisplayAlerts = False 'The excel ghost window lingers on.
' ThisWorkbook.Close , False
' Application.Quit
' Application.DisplayAlerts = True
Application.DisplayAlerts = False 'Ghost window is closed but also kills all instances of excel currently open
Application.Quit
Application.DisplayAlerts = True
'NOTE:
'Have released all excel related objects from memory by setting it to nothing, post use.
End Sub
Snap:
Well, your "gost" problem has the next explanation:
An Excel session/instance means the same Application handler. If you open a workbook from the Excel existing interface, it is open in the same instance. Pressing Ctrl + F6 will jump to the next workbook open in the same instance...
If there are open workbooks not seen in the Ctrl + F6 sequence, this only means that they are open in a different instance.
Another instance is open, for instance :), in this way:
Din ExApp as Object
Set ExApp = CreateObject("Excel.Application")
ExApp.Workbooks.add 'without this line, the instance is quit by itself...
Set ExApp = Nothing 'this only releases the memory
You can see more open Excel instances (if they exist) looking in TaskManager and seeing more the one such application (Excel.exe)...
When you close a workbook, and this specific workbook is the single one of the instance, the application Window, what you name a "gost" remains!. If there are some other workbooks open, the so named "gost" window disappears, too.
In order to handle both situations, please try the next approach:
Private Sub UserForm_Terminate()
If Workbooks.Count > OpenWb Then
ThisWorkbook.Close , False
Else
Application.Quit
End If
End Sub
Function OpenWb() As Long
Dim count As Long, wb As Workbook, arr
For Each wb In Workbooks
arr = Split(wb.Name, ".")
If UCase(arr(UBound(arr))) = "XLSB" Then count = count + 1
Next
OpenWb = count + 1
End Function
Quitting the application is done here only because you asked for it... When you try programmatically to open and close many workbooks, it is more efficient to keep the application open. To open a new instance takes time. To open a workbook in an existing instance takes less time... But to do that, your code must find that existing instance:
Sub testExcelInstance()
Dim Ex As Object
On Error Resume Next
Set Ex = GetObject(, "Excel.Application")
If Ex Is Nothing Then
Err.Clear: On Error GoTo 0
Set Ex = CreateObject("Excel.Application")
End If
On Error GoTo 0
Ex.Workbooks.Add 'just doing something after having the Excel instance object
End Sub
Releasing the objects from the memory does not do anything, in terms of the object itself existence. If physically disappears only if you quit it.

Macro Only Finishes If Run Though VBA Application or Macro List

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

Stop Sub Workbook_BeforeClose from rerunning at close

My problem is when closing my Workbook it reruns the Workbook_BeforeClose Macro. When the user clicks No it closes the ActiveWorkbook and another one that it inputs data to. When the active workbook closes it reruns the message box. It does this because it is part of the close sequence. How do i tell excel to run the code and close without running the code again. Please let me know if i can explain anything more clearly.
Private Sub Workbook_BeforeClose(Cancel As Boolean)
MSG1 = MsgBox("Are you ready to email to MFG?", vbYesNo, "EMAIL MFG")
If MSG1 = vbYes Then
'Attaches current worksheet to new message in Outlook
If MSG1 = vbNo Then
' Enter data into separate work and close all Workbooks
INWBK.Close
Wbk.Close
End Sub
I also get an Error 91 on Wbk.Close, This is the Activeworkbook.
INWBK:
INWBK = ThisWorkbook
Wbk:
Const kPath As String = "C:\"
Const kFile As String = "QUOTE REQUEST LOG 2015.xlsm"
Rem Set Wbk in case it's open
On Error Resume Next
Set Wbk = Workbooks(kFile)
On Error GoTo 0
Rem Validate Wbk
If Wbk Is Nothing Then Set Wbk = Workbooks.Open(kPath & kFile)
The Workbook_BeforeClose sub runs only because the workbook is being closed. Hence there is no need to put INWBK.Close again. After the sub finishes, the workbook window will be closed.
For the Error 91 on Wbk.Close, the error message is Object variable not set. Because you calling the Workbook_BeforeClose twice as explained above, thus Wbk.close is also being called twice. The first time should be a success, but the second time, there is no object bind to it anymore, and thus Excel cried Error 91.
Assuming:
INWBK is the workbook that you are closing (i.e. it has the Workbook_BeforeClose event that is running), and
Wbk is the secondary workbook where "data" is copied to.
There is no need to run INWBK.Close within your Workbook_BeforeClose event handler because INWBK is already in the process of closing. In running INWBK.Close in you code, you are starting another Workbook_BeforeClose event hence the message box being displayed again. You can confirm this by putting a break point on your INWBK.Close command then using F8 to step through the code from there.
I recommend deleting INWBK.Close from your code
An alternative would be to tell Excel to ignore the 2nd close event by adding Application.EnableEvents = False before displaying your message box then run Application.EnableEvents = True before your End Sub.

Saving a different workbook that is opened

I have a workbook (WorkbookA.xlsm) that I open. Upon being opened, this opens WorkbookB.xlsm.
'ThisWorkbook code of WorkbookA.xlsm
Private Sub Workbook_Open()
Dim wb As Workbook
Application.ScreenUpdating = False
Set wb = Workbooks.Open(Filename:="C:\WorkbookB.xlsm")
wb.Windows(1).Visible = False
End Sub
After "B" being opened, a script is called that also sets off a timer.
The script in B changes some data on A. I want to add something after the script is called to automatically save WorkbookA.xlsm as it is (without any prompts).
'ThisWorkbook code of WorkbookB.xlsm
Private Sub Workbook_Open()
Call Script
'looking for something in here to save WorkbookA
End Sub
Since you know the name of the workbook you want saved ("WorkbookA.xlsx"), you can reference it directly with a save method:
Workbooks("WorkbookA.xlsx").Save
You can use the Workbook.Save method to save your workbook. It'll be something like
wb.Save
or
ActiveWorkbook.Save
if you know that your current workbook is the active one. Save doesn't let you change the filename - if you want that, use SaveAs instead.
Try something similar to this:
For each w in Application.Workbooks
If w.Name = "WorkbookA" Then
w.Save
Exit For
End if
Next w
You need to find the workbook you want or set it prior in your code since you want the save code to be called from workbookB. If you were to call it from workbookA, you could use ActiveWorkbook.Save

Way to run Excel macros from command line or batch file?

I have an Excel VBA macro which I need to run when accessing the file from a batch file, but not every time I open it (hence not using the open file event). Is there a way to run the macro from the command line or batch file? I'm not familiar with such a command.
Assume a Windows NT environment.
You can launch Excel, open the workbook and run the macro from a VBScript file.
Copy the code below into Notepad.
Update the 'MyWorkbook.xls' and 'MyMacro' parameters.
Save it with a vbs extension and run it.
Option Explicit
On Error Resume Next
ExcelMacroExample
Sub ExcelMacroExample()
Dim xlApp
Dim xlBook
Set xlApp = CreateObject("Excel.Application")
Set xlBook = xlApp.Workbooks.Open("C:\MyWorkbook.xls", 0, True)
xlApp.Run "MyMacro"
xlApp.Quit
Set xlBook = Nothing
Set xlApp = Nothing
End Sub
The key line that runs the macro is:
xlApp.Run "MyMacro"
The simplest way to do it is to:
1) Start Excel from your batch file to open the workbook containing your macro:
EXCEL.EXE /e "c:\YourWorkbook.xls"
2) Call your macro from the workbook's Workbook_Open event, such as:
Private Sub Workbook_Open()
Call MyMacro1 ' Call your macro
ActiveWorkbook.Save ' Save the current workbook, bypassing the prompt
Application.Quit ' Quit Excel
End Sub
This will now return the control to your batch file to do other processing.
The method shown below allows to run defined Excel macro from batch file, it uses environment variable to pass macro name from batch to Excel.
Put this code to the batch file (use your paths to EXCEL.EXE and to the workbook):
Set MacroName=MyMacro
"C:\Program Files\Microsoft Office\Office15\EXCEL.EXE" "C:\MyWorkbook.xlsm"
Put this code to Excel VBA ThisWorkBook Object:
Private Sub Workbook_Open()
Dim strMacroName As String
strMacroName = CreateObject("WScript.Shell").Environment("process").Item("MacroName")
If strMacroName <> "" Then Run strMacroName
End Sub
And put your code to Excel VBA Module, like as follows:
Sub MyMacro()
MsgBox "MyMacro is running..."
End Sub
Launch the batch file and get the result:
For the case when you don't intend to run any macro just put empty value Set MacroName= to the batch.
you could write a vbscript to create an instance of excel via the createobject() method, then open the workbook and run the macro. You could either call the vbscript directly, or call the vbscript from a batch file.
Here is a resource I just stumbled accross:
http://www.codeguru.com/forum/showthread.php?t=376401
If you're more comfortable working inside Excel/VBA, use the open event and test the environment: either have a signal file, a registry entry or an environment variable that controls what the open event does.
You can create the file/setting outside and test inside (use GetEnviromentVariable for env-vars) and test easily. I've written VBScript but the similarities to VBA cause me more angst than ease..
[more]
As I understand the problem, you want to use a spreadsheet normally most/some of the time yet have it run in batch and do something extra/different. You can open the sheet from the excel.exe command line but you can't control what it does unless it knows where it is. Using an environment variable is relatively simple and makes testing the spreadsheet easy.
To clarify, use the function below to examine the environment. In a module declare:
Private Declare Function GetEnvVar Lib "kernel32" Alias "GetEnvironmentVariableA" _
(ByVal lpName As String, ByVal lpBuffer As String, ByVal nSize As Long) As Long
Function GetEnvironmentVariable(var As String) As String
Dim numChars As Long
GetEnvironmentVariable = String(255, " ")
numChars = GetEnvVar(var, GetEnvironmentVariable, 255)
End Function
In the Workbook open event (as others):
Private Sub Workbook_Open()
If GetEnvironmentVariable("InBatch") = "TRUE" Then
Debug.Print "Batch"
Else
Debug.Print "Normal"
End If
End Sub
Add in active code as applicable. In the batch file, use
set InBatch=TRUE
Instead of directly comparing the strings (VB won't find them equal since GetEnvironmentVariable returns a string of length 255) write this:
Private Sub Workbook_Open()
If InStr(1, GetEnvironmentVariable("InBatch"), "TRUE", vbTextCompare) Then
Debug.Print "Batch"
Call Macro
Else
Debug.Print "Normal"
End If
End Sub
I have always tested the number of open workbooks in Workbook_Open(). If it is 1, then the workbook was opened by the command line (or the user closed all the workbooks, then opened this one).
If Workbooks.Count = 1 Then
' execute the macro or call another procedure - I always do the latter
PublishReport
ThisWorkbook.Save
Application.Quit
End If
# Robert: I have tried to adapt your code with a relative path, and created a batch file to run the VBS.
The VBS starts and closes but doesn't launch the macro... Any idea of where the issue could be?
Option Explicit
On Error Resume Next
ExcelMacroExample
Sub ExcelMacroExample()
Dim xlApp
Dim xlBook
Set xlApp = CreateObject("Excel.Application")
Set objFSO = CreateObject("Scripting.FileSystemObject")
strFilePath = objFSO.GetAbsolutePathName(".")
Set xlBook = xlApp.Workbooks.Open(strFilePath, "Excels\CLIENTES.xlsb") , 0, True)
xlApp.Run "open_form"
Set xlBook = Nothing
Set xlApp = Nothing
End Sub
I removed the "Application.Quit" because my macro is calling a userform taking care of it.
Cheers
EDIT
I have actually worked it out, just in case someone wants to run a userform "alike" a stand alone application:
Issues I was facing:
1 - I did not want to use the Workbook_Open Event as the excel is locked in read only.
2 - The batch command is limited that the fact that (to my knowledge) it cannot call the macro.
I first wrote a macro to launch my userform while hiding the application:
Sub open_form()
Application.Visible = False
frmAddClient.Show vbModeless
End Sub
I then created a vbs to launch this macro (doing it with a relative path has been tricky):
dim fso
dim curDir
dim WinScriptHost
set fso = CreateObject("Scripting.FileSystemObject")
curDir = fso.GetAbsolutePathName(".")
set fso = nothing
Set xlObj = CreateObject("Excel.application")
xlObj.Workbooks.Open curDir & "\Excels\CLIENTES.xlsb"
xlObj.Run "open_form"
And I finally did a batch file to execute the VBS...
#echo off
pushd %~dp0
cscript Add_Client.vbs
Note that I have also included the "Set back to visible" in my Userform_QueryClose:
Private Sub cmdClose_Click()
Unload Me
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
ThisWorkbook.Close SaveChanges:=True
Application.Visible = True
Application.Quit
End Sub
Anyway, thanks for your help, and I hope this will help if someone needs it
I'm partial to C#. I ran the following using linqpad. But it could just as easily be compiled with csc and ran through the called from the command line.
Don't forget to add excel packages to namespace.
void Main()
{
var oExcelApp = (Microsoft.Office.Interop.Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
try{
var WB = oExcelApp.ActiveWorkbook;
var WS = (Worksheet)WB.ActiveSheet;
((string)((Range)WS.Cells[1,1]).Value).Dump("Cell Value"); //cel A1 val
oExcelApp.Run("test_macro_name").Dump("macro");
}
finally{
if(oExcelApp != null)
System.Runtime.InteropServices.Marshal.ReleaseComObject(oExcelApp);
oExcelApp = null;
}
}
I generally store my macros in xlam add-ins separately from my workbooks so I wanted to open a workbook and then run a macro stored separately.
Since this required a VBS Script, I wanted to make it "portable" so I could use it by passing arguments. Here is the final script, which takes 3 arguments.
Full Path to Workbook
Macro Name
[OPTIONAL] Path to separate workbook with Macro
I tested it like so:
"C:\Temp\runmacro.vbs" "C:\Temp\Book1.xlam" "Hello"
"C:\Temp\runmacro.vbs" "C:\Temp\Book1.xlsx" "Hello" "%AppData%\Microsoft\Excel\XLSTART\Book1.xlam"
runmacro.vbs:
Set args = Wscript.Arguments
ws = WScript.Arguments.Item(0)
macro = WScript.Arguments.Item(1)
If wscript.arguments.count > 2 Then
macrowb= WScript.Arguments.Item(2)
End If
LaunchMacro
Sub LaunchMacro()
Dim xl
Dim xlBook
Set xl = CreateObject("Excel.application")
Set xlBook = xl.Workbooks.Open(ws, 0, True)
If wscript.arguments.count > 2 Then
Set macrowb= xl.Workbooks.Open(macrowb, 0, True)
End If
'xl.Application.Visible = True ' Show Excel Window
xl.Application.run macro
'xl.DisplayAlerts = False ' suppress prompts and alert messages while a macro is running
'xlBook.saved = True ' suppresses the Save Changes prompt when you close a workbook
'xl.activewindow.close
xl.Quit
End Sub
You can check if Excel is already open. There is no need to create another isntance
If CheckAppOpen("excel.application") Then
'MsgBox "App Loaded"
Set xlApp = GetObject(, "excel.Application")
Else
' MsgBox "App Not Loaded"
Set wrdApp = CreateObject(,"excel.Application")
End If

Resources