Quick File Snapshots (Save File Backup Script)

Introduction 

When working on projects I’m often making revisions to documents that might require reverting to a previous version. As part of my workflow, before opening a file to make changes, I run a simple script to save a copy of the file (with the file’s date/time appended to the filename) into a _BAK subfolder in the file’s working folder. If I need to revert to the previous version, I can now just overwrite the working file with the backup copy and remove the timestamp from the filename. I can run this backup script from the command line or using the Send to context menu in Windows.

Why not just use git? I do use git for plain-text projects like development work. Using git with branching makes working on text-based files simple, and comparing changes and reverting to earlier versions is pretty simple (once you learn how to use git). I configure the .gitignore file to ignore the _BAK folders and primarily use git rather than the backup script, though I occasionally use the backup script for some files.

I primarily use this backup script for non-text files and projects that aren’t being tracked with git. For binary files (like Photoshop, Word, InDesign, etc) git is too cumbersome and not well-suited to quickly saving and restoring versions (at least compared to a quick copy/paste from the _BAK folder).

Disclaimer

I have been using the scripts below for several years, working on various MS Office and Adobe files and projects. The scripts have worked very well for my workflow. All they do is copy the selected file into a new folder – they make no changes to the original file!

However, your mileage may vary. I make no warranty or guarantee of any kind regarding the safety, usefulness and/or usability of these scripts in your environment. You use them as-is and at your own risk.

Download an Installer 

You can download an installer that will install the script files and set up the Send to menu for you. The installer will create a folder for the script files and add the folder to your system PATH so you can run the script from the command line. If you uninstall the scripts using the standard windows uninstall function, the uninstall will remove the script files and remove the folder from the system PATH automatically.

Download: ZMTC-SaveFileBackup_Installer

Note

The installer is not signed, so Windows might warn you that this is an untrusted file or something similar. The installer was created using Inno Setup and should be safe to run.

The remaining sections below list the scripts and the instructions for manually setting up the Send to context menu.

The Scripts 

Create a folder for the scripts 

You will need to save the following two scripts into a folder on your drive. You can save them into an existing folder or create a new one. If you want to be able to use the script from the command line, you will need to make sure you save the files to a folder that is in your PATH environment variable.

The “Save-File-Backup.ps1” Script 

Copy the following Powershell code into a new file named Save-File-Backup.ps1 and save the file into your folder.

<#
.SYNOPSIS
Save backup copy of a file to a subdirectory
.DESCRIPTION
Makes a backup copy of the filename to a subdirectory named '_BAK'
The script uses the last modified date of the file and appends the
date to the backup file.
.PARAMETER Files 
The comma-separated list of file(s) to back up
.PARAMETER SnapShot 
Use the current date instead of the last modified date
.PARAMETER Pause
Pause the output (useful when running this script from SendTo)
Pause time based on number of files backed up -- 3 seconds per file
.PARAMETER Quiet 
Run quietly (no output to the screen)
.NOTES
Copyright 2020 ZMT Creative LLC
This Script is licensed under a
  Creative Commons Attribution-ShareAlike 4.0 International License. 
  To view a copy of this license, 
  visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter 
  to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
#>
param (
    [Parameter(Mandatory=$true,Position=1)]
    [String[]]$Files,
	[Alias("today","c","now","snap","currentdate")]
    [Switch]$SnapShot=$false,
	[Alias("p")]
    [Switch]$Pause=$false,
	[Alias("q")]
    [Switch]$Quiet=$false
)

function Pause-Host {
    param(
        $Delay = 1
    )
    $counter = 0;
    While(!$host.UI.RawUI.KeyAvailable -and ($counter++ -lt $Delay)) {
        [Threading.Thread]::Sleep(1000)
    }
}

function Backup-File ($Filename) {
    if (-not (Test-Path "$Filename")) {
        Throw "File not found: $Filename"
    }

    $thisFile = Get-ChildItem "$Filename"
    $origFilename = $thisFile.FullName
    $origFilePath = $thisFile.Directory
    $thisFileDate = $thisFile.LastWriteTime
    if ($SnapShot) {
        $FileISODate = Get-Date -Format "\S\N\A\P-yyyyMMddTHHmmss"
    }
    else {
        $FileISODate = $thisFileDate.toString("yyyyMMddTHHmmss")
    }

    if (-not (Test-Path "$($origFilePath)\$($BAKDIR)")) {
        New-Item "$($origFilePath)\$($BAKDIR)" -Type Directory
    }    

    $newFilename = "$($origFilePath)\$($BAKDIR)\$($thisFile.BaseName)-[$($FileISODate)]$($thisFile.Extension)"

    if (Test-Path -LiteralPath "$newFilename") {
        $NewFileExists = $true
    }
    else {
        $NewFileExists = $false
    }

    # If debug is set, output variables instead of running copy
    if ($DebugPreference -eq "Continue") { 
        Write-Debug "---------------"
        Write-Debug "   Original File: $origFilename"
        Write-Debug "    New Filename: $newFilename"
        Write-Debug " Already Exists?: $NewFileExists"
        Return
    }

    if (-not $NewFileExists) {
        Copy-Item "$origFilename" -Destination "$newFilename"
        if (-not $Quiet) {
            if (Test-Path -LiteralPath "$NewFilename") {
                Write-Host " BACKUP: $newFilename" -ForegroundColor "DarkGreen"
            } 
            else {
                Write-Host " FAILED: $origFilename NOT BACKED UP" -ForegroundColor "Red"
            }
        }
    }
    else {
        If (-not $Quiet) { Write-Warning "$Filename EXISTS in _BAK folder" }
    }
}

$BAKDIR="_BAK"

if ($DebugPreference -eq "Inquire") { 
    $DebugPreference="Continue"
    Write-Debug "PARAM:Files: $Filename"
    Write-Debug "PARAM:SnapShot: $SnapShot"
    Write-Debug "PARAM:Quiet: $Quiet"
}

$MyDelay = 3

foreach ($File in $Files) { 
    Backup-File($File)
    $MyDelay++
}

if ($Pause) { Pause-Host($MyDelay) }

The “Save-File-Backup.cmd” Wrapper Script 

As noted in the batch script below, this wrapper is needed for handling multiple files on the command line, especially when using the Send to context menu in Windows. The Send to menu sends multiple selected files to the script using standard CMD syntax (each file separated by a space), but Powershell wants comma-separated lists, so this wrapper attempts to convert the file list into the comma-separated format before calling the Powershell script to do the actual file copy.

Copy the following batch code into a new file named Save-File-Backup.cmd and save the file into your folder.

@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
REM 
REM Wrapper script for Save-File-Backup Powershell script
REM IMPORTANT: This wrapper is needed for handling multiple files on the
REM            command line when using SendTo. SendTo sends multiple files
REM            using standard CMD syntax [each file separated by a space],
REM            but PowerShell wants Comma-Separated lists, so this wrapper
REM            converts to the comma-separated format and calls the
REM            Powershell script to do the actual Save-File-Backup.
REM 
REM Copyright 2020 ZMT Creative LLC
REM 
REM This work is licensed under the 
REM   Creative Commons Attribution-ShareAlike 4.0 International License. 
REM   To view a copy of this license, 
REM   visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter 
REM   to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
REM


SET PSScript=%~dpn0.ps1
IF '%1'=='-help' GOTO :SHOWHELP
IF '%1'=='--help' GOTO :SHOWHELP
IF '%1'=='-h' GOTO :SHOWHELP
IF '%1'=='-?' GOTO :SHOWHELP
SET args='%~1'
:MORE
shift
IF '%1'=='' GOTO :DONE
SET args=!args!,'%~1'
GOTO :MORE

:SHOWHELP
%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -Command "& get-help -Name '%PSScript%' -detailed"
EXIT /B 0

:DONE
%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -Command "& '%PSScript%' -Pause -Files !args!"
EXIT /B 0

Add Shortcut to the “Send to” Context Menu 

Now that you have created the two scripts, you will need to create a shortcut for the Save-File-Backup.cmd wrapper script and add the shortcut to the Send to menu.

Create a Shortcut for the Wrapper Script 

Create a shortcut for the Save-File-Backup.cmd wrapper script. In Windows File Explorer, locate the folder with the scripts. Right-click on the Save-File-Backup.cmd file and select Create shortcut from the menu. This will create a new file, likely named Save-File-Backup.cmd - Shortcut (or something similar). Rename this shortcut file to _Save File Backup (no extension and with an underscore as the first character).

Note

The underscore at the beginning of the filename is optional, but will sort the file to the top of any context menu or other file listing.

Info

The shortcut can be named anything you want – it just points to the Save-File-Backup.cmd wrapper script. However, if you rename the Powershell script or the wrapper script at a later date, you will need to recreate this shortcut.

Copy the Shortcut to your “Send to” Folder 

Right-click on the shortcut file (in this example, the _Save File Backup file you just created) and select Copy.

Press ⊞ Win + R to open the Run dialog box, type shell:sendto and press the Ok button. This will open your Send to folder in Windows File Explorer.

Right-click on the Send to window and select Paste from the menu to complete the copy. If all went well, the _Save File Backup should now appear under the Send to context menu when you right-click on any file.

Owner & Founder

Related