Personal Backup Scripting Example

I’m using Personal Backup by Dr. Rathlev as my backup tool. It’s name is a bit misleading, because you could think that it is only allowed to use it as a private person. But no – its current license says it may be used also by any club, organization or even private companys for free.

It is easy to create backup tasks with Personal Backup. But a backup task does not run automatically. And I have to say, it is not easy to create backups that run automatically with this program. The UI of the part of the program which deals with creating automatically running backup tasks is near incomprehensible – even when you read the docs.

What compensates this deficit is that Personal Backup has a perfect command line interface with a good docmentation. So it is very well suited for being scripted.

I control my backups with some tclkit scripts. As you might know, I do use tclkit for many tasks.

The script below creates a full backup in every uneven month (Jan, Mar, May, …) and in between it creates an incremental backup daily. It needs a fitting Personal Backup task file Backup-Task-All.buj.

To trigger the script daily, I use the Windows Task Planner.

The directory structure of the created backups looks like this:

G:/Backup/2014/BD01F   # A full backup created on March 1st in 2014
G:/Backup/2014/BD22I   # An incremental backup created on March 22nd
G:/Backup/2014/BD21I   # An incremental backup from September 21st 
#  This script steers the backups that I do with Pesonal Backup.
# To adapt the script, just set the following variables taskName,
# rootdir and personalBackupExe to your liking. 

# The name of the backup task in PB
set taskName Backup-Task-All

# The name of the target root directory.  This must be the same as 
# in the Backup-Task mentioned above.
set rootdir G:/Backup

# Full path to the Personal Backup executable
set personalBackupExe "C:/Program Files/Personal Backup 5/Persbackup.exe"

#console show

puts "BackupSteer.tcl  (c) A.J.W 2014"

set rootdrive [string tolower [string range $rootdir 0 0]]
set seconds [clock seconds]
set yearMonth [clock format $seconds -format {%Y/%m}]
# the name of the basedir of the backup of today. E.g  G:/Backup/2014/09
set basedir [file join $rootdir $yearMonth]

puts "    basedir=$basedir"
puts "    taskName=$taskName"

# Return 1 if it's an even month, 0 if not.  
proc isEvenMonth { seconds } {
    set month [clock format $seconds -format {%m}]
    if { [string range $month 0 0] == 0 } {
        set month [string range $month 1 1]
    set even [expr $month % 2 == 0] 
    return $even 

# Return a list of all connected drives. 
proc drives {} {
    foreach drive [list a b c d e f g h i j k l m n o p q r s t u v x y z] {
        if {[catch {file stat ${drive}: dummy}] == 0} {
            lappend drives $drive
    return $drives

proc doBackup {} {
    global  taskName  seconds yearMonth basedir  personalBackupExe 

    set relativeDirForFullBackup BD01F
    set pathForFullBackup [file join $basedir $relativeDirForFullBackup]
    puts "    pathForFullBackup=$pathForFullBackup"

    # if full backup for this month already exists or we have an even month
    if { [file isdirectory $pathForFullBackup] || [isEvenMonth $seconds] } {
        # only do an incremental backup
        set mode incr
        set day [clock format $seconds -format {%d}]
        set relDir BD$day
        append relDir I
        set pathForIncrBackup [file join $basedir $relDir]
        puts "    pathForIncrBackup=$pathForIncrBackup"

        if [file isdirectory $pathForIncrBackup] {
            set mode none

    } else {
        # do a full  backup
        set mode full
        set relDir $relativeDirForFullBackup

    puts "    mode=$mode"
    puts "    relDir=$relDir"

    if { $mode != "none" } {
        puts "    Running backup like this:"
        puts "    $personalBackupExe $taskName /force /hide /mode:$mode /prompt:delay  /backupdir:$relDir & "
        # If I do not add the & at the end, Persbackup hangs. 
        exec $personalBackupExe $taskName /force /hide /mode:$mode /prompt:delay  /backupdir:$relDir &
        puts "     Backup running." 
    } else {
        puts "    Nothing to do."

if { [lsearch [drives] $rootdrive] == -1 } {
    bell; bell; bell
    set a "The daily backup is about to be done. "
    append a "Please connect the backup drive, make it "
    append a  "available as network drive $rootdrive: and click Ok."

    message .m  -textvariable a -width 250
    # strange, the width of the button is in another dimension than that of the message. 
    button .hello  -text "Ok" -command { 
            if { [lsearch [drives] $rootdrive] != -1 } { doBackup; exit } else { bell }
        } -default active -width 15

    bind .  {.hello invoke}

    pack  .m .hello -padx 5 -pady 5

    wm deiconify .
} else {