Building Your Own (And Better) Autosave Feature for Excel

Because the current Excel Autosave is just so bad XD

me@jaykilleen.com wrote this almost 6 years ago and it was last updated almost 6 years ago.


← Back to the Posts

Have you ever been in that situation where you've been 'in the zone' for ages and then all of a sudden Excel crashes. You're just like 'Are... you... (*&^ing.. kidding me!'. Then you have this moment where, you're thinking 'Sure Excel Autosave is going to work this time! It never works but this time it just has to.' Excel opens back up and says "We've recovered some of your files" and then shows you that the last recovered time, is just the last time you actually saved it. "Hey we recovered your file just by going into your files and opening the last time you saved it. Ain't we clever!" NO EXCEL, what the!! Really?!

Anyway, I know that is a bit of a brain dumping rant but I go through this enough to know that Excel Autosave WILL DESTROY YOUR SOUL. Maybe 1%.

And seeings as we can't use git for Excel VBA development, I finally got sick of losing code with zero notice.

To fix this I figured out how to run a job every x minutes in Excel that would simply ask the question "Jay! When did you last save? If it's too long ago, then I'm gonna prompt you to save your work. If it is AAAAGES ago then I am gonna force you to save your work!"

It is also a nice pattern on how to schedule jobs in your Excel. So you can also extend this code to do other things like 'Remind you to stand up, or take a walk or drink some water :)'. Maybe you need a job that sends an email that triggers your IFTTT that turns your office lights off at 6PM so you can go home to your family.

So below, if you copy the code in ThisWorkbook.bas to your ThisWorkbook object. Then create two new modules called Job and Functions and copy those code snippets in then things should work. They probably won't just work but they should get you started.

I've also allowed you to drop zzz into your terminal to turn Jobs off while you are writing code, because running jobs on an increment while you are in your IDE can be a pain. So please! remember this because it is one thing to be working in Excel and having a better AutoSave feature and another to be working in VBA IDE and having a better autosave feature. This one works best for working in your Excel worksheet.

I also use OneDrive for Business to back my files up to the cloud. You might not need to use my wbLocalPath function if you normally store your file locally to begin with. This function just converts a OneDrive for Business or sharepoint url back to your local C Drive path so that the vba actually knows where the workbook is so that it can check it's last saved time.

I'll add any improvements to this code later. It needs error handling for example but for now it works enough to be better than what you get from Excel Out of the Box.

Enjoy! :D

ThisWorkbook.bas

Option Explicit

Private Sub Workbook_Open()
  Call Job.KillAllJobs
  Call Job.TrackLastSavedAt
End Sub

Private Sub Workbook_AfterSave(ByVal Success As Boolean)
  Application.StatusBar = "Last Saved " & LastSavedTimeStamp
  Call Job.KillAllJobs
  Call Job.TrackLastSavedAt
End Sub

Job.bas

Option Explicit

Public subName As String
Public subInterval As Date
Public PauseJobs As Boolean

' You can schedule multiple jobs by passing the Call to ScheduleJob with the new name and interval

Sub zzz()
  Call TurnJobsOff
End Sub

Sub TurnJobsOff()
  Clear
  PauseJobs = True
  Debug.Print Now(); ": Jobs paused"
End Sub

Sub TurnJobsOn()
  Clear
  PauseJobs = False
  Debug.Print Now(); ": Jobs unpaused"
  
  'List all jobs to turn back on below.
  TrackLastSavedAt
End Sub

Sub TrackLastSavedAt()
  
  If ActiveWorkbook.Name = "PUT YOUR WORKBOOK NAME HERE.xlsm" Then
  
    Dim sLastSavedAt As String: sLastSavedAt = LastSavedTimeStamp
    Dim minutes As Integer: minutes = DateDiff("n", sLastSavedAt, Now())
    
    Application.ScreenUpdating = False
    
    subName = "TrackLastSavedAt"
    subInterval = Now + TimeValue("00:00:30")
    
    'DO THE STUFF FOR THE JOB HERE
    If PauseJobs Then Debug.Print Now(); ": Jobs last paused"
    If PauseJobs = False Then
      Debug.Print Now(); ": TrackLastSaved has been run."
      If minutes > 5 And minutes <= 10 Then
        Application.StatusBar = sLastSavedAt & " - Last Checked At: " & Now & " - !!! This is over " & minutes & " minutes ago !!!"
      ElseIf minutes > 10 And minutes <= 20 Then
        Application.StatusBar = sLastSavedAt & " - Last Checked At: " & Now & " - !!! This is over " & minutes & " minutes ago !!!"
        If minutes Mod 5 = 0 Then
          If MsgBox("This workbook hasn't been saved in " & minutes & " minutes! Do you want to save?", vbYesNo, "Save?") = vbYes Then ThisWorkbook.Save
        End If
      ElseIf minutes > 20 Then
        Application.StatusBar = sLastSavedAt & " - Last Checked At: " & Now & " - !!! This is over " & minutes & " minutes ago !!!"
        If minutes Mod 2 = 0 Then
          If MsgBox("This workbook hasn't been saved in " & minutes & " minutes! Do you want to save?", vbYesNo, "Save?") = vbYes Then ThisWorkbook.Save
        End If
      Else
        Application.StatusBar = sLastSavedAt & " - Last Checked At: " & Now
      End If
    End If
    '---------------------------------------------------------
    
    Application.ScreenUpdating = True
  
  End If
  
  If PauseJobs = False Then Call ScheduleJob(subName, subInterval)
End Sub


' =======================================================

Sub ScheduleJob(p As String, s As Date)
  If ActiveWorkbook.Name = "PUT YOUR WORKBOOK NAME HERE.xlsm" Then
    Application.OnTime _
      earliesttime:=s, _
      procedure:=p, _
      schedule:=True
  End If
End Sub

Sub KillAllJobs()
  If ActiveWorkbook.Name = "PUT YOUR WORKBOOK NAME HERE.xlsm" Then
    Call KillJob("TrackLastSavedAt")
    Application.StatusBar = False
  End If
End Sub

Sub KillJob(p As String)
  On Error Resume Next
  Application.OnTime _
    earliesttime:=Now, _
    procedure:=p, _
    schedule:=False
End Sub

Features.bas

Option Explicit

Function LastSavedTimeStamp() As String
  LastSavedTimeStamp = ActiveWorkbook.BuiltinDocumentProperties("Last Save Time")
  ' Exclude Autosave!
  LastSavedTimeStamp = FileDateTime(wbLocalPath(ThisWorkbook))
End Function

Sub ShowLastSavedTimeStamp()
  Dim hours As Integer: hours = DateDiff("h", LastSavedTimeStamp, Now())
  Dim minutes As Integer: minutes = DateDiff("n", LastSavedTimeStamp, Now())
  Dim seconds As Integer: seconds = DateDiff("s", LastSavedTimeStamp, Now())
  MsgBox "This workbook was Last Saved at " & vbNewLine & LastSavedTimeStamp & " or " & hours & ":" & (minutes Mod 60) & ":" & (seconds Mod 60) & " ago"
End Sub


Function wbLocalPath(ByRef wb As Workbook) As String

  Dim Ctr As Long
  Dim objShell As Object
  Dim UserProfilePath As String

  'Check if it looks like a OneDrive location
  If InStr(1, wb.FullName, "https://", vbTextCompare) > 0 Then

    'Replace forward slashes with back slashes
    wbLocalPath = Replace(wb.FullName, "/", "\")

    'Get environment path using vbscript
    Set objShell = CreateObject("WScript.Shell")
    UserProfilePath = objShell.ExpandEnvironmentStrings("%UserProfile%")

      'Trim OneDrive designators
    For Ctr = 1 To 6
        'Debug.Print wbLocalPath
       wbLocalPath = Mid(wbLocalPath, InStr(wbLocalPath, "\") + 1)
        'Debug.Print wbLocalPath
    Next

      'Construct the name
    wbLocalPath = UserProfilePath & "\OneDrive - COMPANY NAME\" & wbLocalPath

  Else

    wbLocalPath = wb.FullName

  End If
  
  'Just do a quick print and check that the paths are working ok. You might need to change the `Ctr = 1 To 6` part to get the right number of '\'
  'Debug.Print "This Workbook.LocalPath is:"
  'Debug.Print wbLocalPath
  
End Function