ActiveWorkbook.SaveCopyAs Filename: gives
Runtime error 1004
but hitting Debug>Run> Continue code runs as expected.
I have a macro enable Excel2016 spreadsheet. When updated and saved to PC I also wish to save a copy to my NAS. I have written code (see below) and used identical code, other than filename, for two other spreadsheets. These other two spreadsheets are saved as expected (i.e. to NAS and PC with no Runtime error))
Code as follows:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
'Saves the current file to a backup folder and the default folder
'Note that any backup is overwritten
Application.DisplayAlerts = False
ActiveWorkbook.SaveCopyAs Filename:="\\ReadyNasDuo\Dell\Excelbak\finance18_19.xlsm"
ActiveWorkbook.Save
Application.DisplayAlerts = True
End Sub
I would expect problem file to act like the other two. Anyone any ideas why it does not. Only differences are that problem file is a lot larger, is password protected, has link to another spreadsheet and has far more 'coding'(i.e. more macros and VBA)
You must also deactivate the events Application.EnableEvents = False otherwise your Save/SaveCopyAs will trigger another Workbook_BeforeSave event.
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
'Saves the current file to a backup folder and the default folder
'Note that any backup is overwritten
Application.DisplayAlerts = False
Application.EnableEvents = False
ThisWorkbook.SaveCopyAs Filename:="\\ReadyNasDuo\Dell\Excelbak\finance18_19.xlsm"
'ActiveWorkbook.Save 'needed?
Application.EnableEvents = True
Application.DisplayAlerts = True
End Sub
Also I think you don't need ActiveWorkbook.Save because it will save at End Sub anyway as long as you don't set Cancel = True. The event is called BeforeSave not InsteadOfSave so the original save action will still happen when BeforeSave finished.
Note that ThisWorkbook and ActiveWorkbook are not the same. I assume that you meant to use ThisWorkbook which is the workbook this code is running in, while ActiveWorkbook is the one that has the focus (is on top) while the code is running.
Related
I have a form for work that I update and email out daily. I open from one master form ("Passdown Report 3rd Shift"), and save-as individual copies separated by the date ("Passdown Report 3rd Shift 2022-01-19") before I leave each day.
The form is filled with formulas that auto-update based on the day and based off stimuli from the other worksheets in the workbook, so I added a macro to convert all formulas in the range to their values.
I want to run this macro before I save the form as the daily file, but not when I'm simply updating and saving the master file. Is that something I can do?
Run Macro Before Closing a Workbook
It is expected that you SaveAs as the backup.
Only when closing the backup, the IF(StrComp... will note a different file name, and your macro will run and the backup will be saved again before closing.
It is kind of clumsy, but it should ensure the safety of your original.
The problem with BeforeSave is that you could do If SaveAsUI = True Then but you could also accidentally do the SaveAs on the original and have it 'destroyed'. I consider it too risky.
Option Explicit
Private ClosingBackup As Boolean
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Const sFileName As String = "Passdown Report 3rd Shift.xlsm"
If Not ClosingBackup Then
With Me
' Compare if you're closing the original or the backup.
If StrComp(.Name, sFileName, vbTextCompare) <> 0 Then ' backup
MyMacro
ClosingBackup = True
' Here it will again call this sub but will exit because
' 'ClosingBackup' is set to True ('If Not ClosingBakup Then').
.Close SaveChanges:=True
'Else ' ClosingBackup = False i.e. closing the original; do nothing
End If
End With
'Else ' ClosingBackup = True; closing the backup which was saved; do nothing
End If
End Sub
You can use the function BeforeSave, on your ThisWorkbook.
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
If SaveAsUI then
'Your code
end if
End Sub
In the microsoft documentation is written on the popup of the SaveAs, this code will be executed and the flag SaveAsUI will be true.
https://learn.microsoft.com/en-us/office/vba/api/excel.workbook.beforesave
Note: You will need to make it an .xlsm file if you want your macro te be saved...
I have an excel Workbook that uses Power Query to populate a table. The query pulls information from multiple external Workbooks as well as a table in a Worksheet within the same Workbook.
The Worksheet in the same Workbook also stores changes to columns that are meant to be manipulated. The Worksheet in the same Workbook is part of a loop. It stores information that is pulled into the main table but also stores the changes made.
To make this work correctly, the Workbook needs to be Saved before the storage query runs. If a Save does not occur before running the query, the query will not contain the changes.
This is accomplished easily when clicking the save button. The following code works well:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Application.EnableEvents = False
ThisWorkbook.Save
ThisWorkbook.Connections("Query - Stored").Refresh
Application.EnableEvents = True
End Sub
This does not work when the workbook is closed and save is clicked after clicking the document close.
The BeforeSave event does not fire because it does not get a chance to. The document closes and when it is reopened it shows it as a crashed file and is listed in the recovery list.
Can anyone help me understand why and how to overcome it.
Since this is caused by an Excel crash while closing, it's possible that the Refresh gets messed up when Close is underway.
You could try executing the Refresh before Closing, like this
Private Closing As Boolean
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Closing = True
Application.EnableEvents = False
ThisWorkbook.Save
ThisWorkbook.Connections("Query - Stored").Refresh
Application.EnableEvents = True
End Sub
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
If Closing Then Exit Sub
Application.EnableEvents = False
ThisWorkbook.Save
ThisWorkbook.Connections("Query - Stored").Refresh
Application.EnableEvents = True
End Sub
So this is strange to me, but I have a macro-enabled excel 2016 file. The only macro in the file is a BeforeSave event, which is stored in ThisWorkbook. After using the blank file once or twice, it gets to the point where opening the file and doing anything at all, like clicking File or Developer or entering data, causes excel to "Stop Working" and close.
Below is the BeforeSave event which is the only macro in this file (there are no modules or userforms, nothing else in ThisWorkbook).
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Application.EnableEvents = False
Cancel = True
ThisWorkbook.Sheets("Pending").Columns(9).NumberFormat = "#"
ActiveWorkbook.Save
Application.EnableEvents = True
End Sub
This basic macro event will work perfectly for a few times. Then after some data is in the file, the next time it is opened the issue will occur. This is the only excel file that experiences this crash, I can still open the original backup file with this macro that does not have data in it yet, and it will be okay.
I have tried opening the file in Safe Mode, and I have installed all the latest Microsoft Office updates.
Has anyone else experienced an issue like this? Does it have something to do with the BeforeSave event macro?
UPDATE:
I changed the ActiveWorkbook to ThisWorkbook. Also, I have changed from editing the entire column to now finding the last used row and formatting that range excluding the header row.
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Application.EnableEvents = False
Cancel = True
Dim Lastrow As Integer
Lastrow = ThisWorkbook.Sheets("Pending").Cells(Rows.Count, 9).End(xlUp).Row
ThisWorkbook.Sheets("Pending").Range("I2:I" & Lastrow).NumberFormat = "#"
ThisWorkbook.Save
Application.EnableEvents = True
End Sub
Thank you.
As Zack & GWD advised in the comments, I updated the VBA to factor in the header row and set all references to ThisWorkbook instead of including ActiveWorkbook. So far the error has not occurred again. If it does happen in the future, I will post a new question if I cannot solve the issue.
Here is the updated code for reference:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Application.EnableEvents = False
Cancel = True
Dim Lastrow As Integer
Lastrow = ThisWorkbook.Sheets("Pending").Cells(Rows.Count, 9).End(xlUp).Row
ThisWorkbook.Sheets("Pending").Range("I2:I" & Lastrow).NumberFormat = "#"
ThisWorkbook.Save
Application.EnableEvents = True
End Sub
When opening my sheet, I create a backup file of the current workbook almost without any delay.
When I close the file, another copy of the workbook is created also almost without any delay.
When saving the master file, Excel waits a long time (sometimes it takes several minutes) before saving.
The master file is not large, only 1.05Mb. I tried speedup solutions available on the net. Even tried reinstalling Excel.
This is the coding.
Private Sub Workbook_Open()
... ' perform some actions like setting NewName and ext
ActiveWorkbook.SaveCopyAs (NewName & " (backup)." & ext)
....
End Sub
Private Sub Workbook_Deactivate()
... ' perform some actions like setting NewName and ext
ActiveWorkbook.SaveCopyAs (NewName & " (backup)." & ext)
....
End Sub
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
If SaveAsUI Then Cancel = True 'Excel will close and handle saving itself.
If ActiveWorkbook.Saved Then Cancel = True ' Cancel saving when no changes were made
...
Application.EnableEvents = False
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.DisplayAlerts = False ' Make sure no close message pops up from the application
ActiveWorkbook.Save
Application.DisplayAlerts = True
ActiveWorkbook.Saved = True
.... ' Processing stuff amongst with resetting ScreenUpdating, EnableEvents and Calculation
Cancel = True ' Or it will fire twice for some reason
End Sub
Upon closing Excel I let it handle all the saving itself.
The workbook and copies are stored on a file server via a 1Gb network. The network isn't the problem, since the copies are saved very quickly. It is the master file that is saved sluggishly. It seems to be even slower when I work longer with the workbook.
I recently switched from Office 2007 to Office 2016 (=365). Before the switch there were no problems.
The Workbook_BeforeSave event runs every time the current workbook is saved. When it completes, the workbook is saved unless Cancel has been set to True (at any point in the subroutine). Setting Cancel = True stops the workbook from being saved when the subroutine finishes.
The problem is when you call the Save method within the Workbook_BeforeSave event, it immediately triggers the same event again, which then tries to save the workbook again and then triggers the event again. This is repeated until you reach the limit of the number of the nested sub-routines that can run simultaneously.
SaveCopyAs saves a copy of the workbook, which doesn't trigger the BeforeSave Event, so it just saves a copy of the workbook once quickly.
Maybe 2007 didn't allow this recursive calling of the Workbook_BeforeSave event or it had a smaller limit of number of nested sub-routines.
Setting Cancel to True doesn't stop the event in its tracks, it just stops Excel from saving the workbook again after the subroutine has finished. If your intention is to not manually handle the saving when there have been no changes (detected by SaveAsUI = True), then perhaps you should just exit the sub at that point:
If SaveAsUI Then Exit Sub
The subroutine will then stop without calling the save method, but the workbook will still be saved when the sub finishes, ending the recursive loop.
You can test whether the file has already been saved before firing the save event:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
If SaveAsUI Then Cancel = True 'Excel will close and handle saving itself.
If ActiveWorkbook.Saved = False Then
Application.DisplayAlerts = False
Application.EnableEvents = False
ActiveWorkbook.Save
Application.DisplayAlerts = True
Application.EnableEvents = True
'Debug.Print "Fired"
End If
End Sub
After extensive testing I come to this conclusion: the difference between saving a copy or the master file is that when the master file is saved, also temporary files and cash(?) is processed. This leads to extra delay before saving a file. This is the only reason I can find that explains the difference in behaviour between saving a copy or a master file.
How can the following conditions be met with VBA code?
A particular worksheet is always displayed on open, even if the worbook is opened without enabling macros.
A workbook user may save the workbook while working on any worksheet.
The save must not interfere with the user - no navigating away to a different sheet, no messageboxes, etc.
The regular save functions (Ctrl-S, clicking Save) must remain available and when used must obey the criteria above.
I'd like to avoid the attempted solutions I've listed at the bottom of this question.
Details:
The workbook is created using Office 2007 on a Windows 7 machine. It is an .xlsm workbook with 2 worksheets, "Scheduler" and "Info." Sheet tabs are not visible. Not all users will enabled macros when the workbook is opened.
Upon opening the workbook, a user will only be exposed to one sheet as follows:
"Info" shows up if macros are disabled, and basically tells anyone who opens the workbook that macros need to be enabled for full workbook functionality. If macros are enabled at this point, "Scheduler" is activated.
"Scheduler" is where data is stored and edited, and is automatically shown if macros are enabled. It is not presented to the user when the workbook is opened without macros enabled.
"Info" must show up first thing if the workbook is opened and macros are disabled.
Attempted Solutions (I'm looking for better solutions!):
Placing code in the Workbook.BeforeSave event. This saves with "Info" activated so it shows up when the workbook is opened. However, if the user is in "Scheduler" and not done, I cannot find a way in this event to re-activate "Scheduler" after the save.
Using Application.OnKey to remap the Ctrl-s and Ctrl-S keystrokes. Unfortunately this leaves out the user who saves using the mouse (clicking File...Save or Office Button...Save).
Checking during every action and if needed activating "Scheduler". In other words, inserting code in something like the Workbook.SheetActivate or .SheetChange events to put "Scheduler" back into focus after a save with "Info" activated. This runs VBA code constantly and strikes me as a good way to get the other code in the workbook into trouble.
Placing code in the Worksheet("Info").Activate event, to change focus back to "Scheduler". This leads to the result of "Scheduler", not "Info", showing when the workbook is opened, even with macros disabled.
Will this not work? Updated to handle Saving gracefully
Private Sub Workbook_Open()
ThisWorkbook.Worksheets("Scheduler").Activate
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
ThisWorkbook.Worksheets("Info").Activate
If (ShouldSaveBeforeClose()) Then
Me.Save
Else
Me.Saved = True ' Prevents Excel Save prompt.
End If
End Sub
Private Function ShouldSaveBeforeClose() As Boolean
Dim workbookDirty As Boolean
workbookDirty = (Not Me.Saved)
If (Not workbookDirty) Then
ShouldSaveBeforeClose= False
Exit Function
End If
Dim response As Integer
response = MsgBox("Save changes to WorkBook?", vbYesNo, "Attention")
ShouldSaveBeforeClose= (response = VbMsgBoxResult.vbYes)
End Function
I don't have time to test this out, but you might be able to do this using Application.OnTime in your BeforeSave event handler. Something like:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Dim objActiveSheet
Set objActiveSheet = Me.ActiveSheet
If objActiveSheet Is InfoSheet Then Exit Sub
If Module1.PreviousSheet Is Nothing Then
Set Module1.PreviousSheet = objActiveSheet
InfoSheet.Activate
Application.OnTime Now, "ActivatePreviousSheet"
End If
End Sub
Then in Module1:
Public PreviousSheet As Worksheet
Public Sub ActivatePreviousSheet()
If Not PreviousSheet Is Nothing Then
PreviousSheet.Activate
Set PreviousSheet = Nothing
End If
End Sub
Edit 2: Here is a re-write that does not utilize AfterSave. You may need to tweak the dialog created from GetSaveAsFilename according to your needs.
This relies on overriding default save behavior and handling the save yourself.
Private actSheet As Worksheet
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Cancel = True
PrepareForSave
manualSave SaveAsUI
AfterSave ThisWorkbook.Saved
End Sub
Private Sub PrepareForSave()
Set actSheet = ThisWorkbook.ActiveSheet
ThisWorkbook.Sheets("Info").Activate
hidesheets
End Sub
Private Sub manualSave(ByVal SaveAsUI As Boolean)
On Error GoTo SaveError 'To catch failed save as
Application.EnableEvents = False
If SaveAsUI Then
If Val(Application.Version) >= 12 Then
sPathname = Application.GetSaveAsFilename(FileFilter:="Excel Files (*.xlsm), *.xlsm")
If sPathname = False Then 'User hit Cancel
GoTo CleanUp
End If
ThisWorkbook.SaveAs Filename:=sPathname, FileFormat:=52
Else
sPathname = Application.GetSaveAsFilename(FileFilter:="Excel Files (*.xls), *.xls")
If sPathname = False Then
GoTo CleanUp
End If
ThisWorkbook.SaveAs Filename:=sPathname, FileFormat:=xlNormal
End If
Else
ThisWorkbook.Save
End If
SaveError:
If Err.Number = 1004 Then
'Cannot access save location
'User clicked no to overwrite
'Or hit cancel
End If
CleanUp:
Application.EnableEvents = True
End Sub
Private Sub AfterSave(ByVal bSaved As Boolean)
showsheets
If actSheet Is Nothing Then
ThisWorkbook.Sheets("Scheduler").Activate
Else
actSheet.Activate
Set actSheet = Nothing
End If
If bSaved Then
ThisWorkbook.Saved = True
End If
End Sub
Private Sub hidesheets()
For Each ws In ThisWorkbook.Worksheets
If ws.Name <> "Info" Then
ws.Visible = xlVeryHidden
End If
Next
End Sub
Private Sub showsheets()
For Each ws In ThisWorkbook.Worksheets
ws.Visible = True
Next
End Sub
Private Sub Workbook_Open()
AfterSave True
End Sub
The only way to make Info display first without macros enabled is if that is how the workbook was saved. This is most reasonably handled when saving.
Unless I misunderstood your issue, not using BeforeSave seems misguided. Just make sure to use AfterSave as well. Here's an example:
Private actSheet As Worksheet
Private Sub Workbook_AfterSave(ByVal Success As Boolean)
showsheets
actSheet.Activate
Set actSheet = Nothing
Thisworkbook.Saved = true 'To prevent save prompt from appearing
End Sub
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Set actSheet = ThisWorkbook.activeSheet
ThisWorkbook.Sheets("Info").Activate
hidesheets
End Sub
Private Sub Workbook_Open()
showsheets
ThisWorkbook.Sheets("Scheduler").Activate
End Sub
Private Sub hidesheets()
For Each ws In ThisWorkbook.Worksheets
If ws.Name <> "Info" Then
ws.Visible = xlVeryHidden
End If
Next
End Sub
Private Sub showsheets()
For Each ws In ThisWorkbook.Worksheets
ws.Visible = True
Next
End Sub
The use of the private object actSheet allows the "ActiveSheet" to be reselected after save.
Edit: I noticed you had more requirements in the comments. The code has been updated so that now upon saving, only the Info sheet will be visible, but when opened or after saving, every sheet will reappear.
This makes it so that any user opening the file without macros will not be able to save with a different sheet activated, or even view the other sheets. That would certainly help motivate them to enable macros!
This problem has been flogged to death in the past, its just hard to find a solution that actually works. Take a look at this code which should do what you need. Basically it shows a splash screen, with all other sheets hidden if the user does not enable macros. It will still save normally if the user clicks save and wont interfere with their work. If they save with there worksheet open it will still show only the splash screen when next opened. Download the sample file below and you can test for yourself, make sure you download the file posted by Reafidy it has over 400 views. If you need it modified further let me know.
Private Sub Workbook_BeforeClose(Cancel As Boolean)
bIsClosing = True
End Sub
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Dim wsArray() As Variant
Dim iCnt As Integer
Application.ScreenUpdating = 0
Splash.Visible = True
For Each wsSht In ThisWorkbook.Worksheets
If Not wsSht.CodeName = "Splash" Then
If wsSht.Visible = True Then
iCnt = iCnt + 1: Redim Preserve wsArray(1 To iCnt)
wsArray(iCnt) = wsSht.Name
End If
wsSht.Visible = xlSheetVeryHidden
End If
Next
Application.EnableEvents = 0
ThisWorkbook.Save
Application.EnableEvents = 1
If Not bIsClosing Then
For iCnt = 1 To UBound(wsArray)
Worksheets(wsArray(iCnt)).Visible = True
Next iCnt
Splash.Visible = False
Cancel = True
End If
Application.ScreenUpdating = 1
End Sub
Private Sub Workbook_Open()
Dim wsSht As Worksheet
For Each wsSht In ThisWorkbook.Worksheets
wsSht.Visible = xlSheetVisible
Next wsSht
Splash.Visible = xlSheetVeryHidden
bIsClosing = False
End Sub
A sample file can be found here.
How about using a 'proxy workbook'.
The 'proxy workbook'
is the only workbook which is directly opened by the users
contains the info sheet
contains VBA to open your 'real workbook' using Workbooks.Open (As I've checked with Workbooks.Open documentation by default it will not add the file name to your recent files history unless you set the AddToMru argument to true)
if required the VBA code could even make sure that your 'target workbook' is trusted (I found some sample code here)
The 'target workbook'
contains your Schedule and any other sheets
is only opened if the VBA code in 'proxy workbook' was executed
can be saved by the user at any time as usual
I've got no Office 2007 at hand to test this but think it should do.