Windows backups for an idiot

Today, I’m writing this note about backup procedures for Windows. Up to now, I’ve been backing up my Windows machines (operating currently five) in an ad hoc manner. Cringe you may, but I’d only made a backup once a month or so. Well, for the many decades I’ve been working with computers, I’ve never lost that much, through disk failures, unexpected file deletions, and upgrades. If a machine got trashed, I could mount the driver in another machine, boot a good OS, copy the files I’m interested in, and start afresh with a reformatted/reinstalled OS. But, my luck changed unexpectedly last week.

The recent Windows 10 Creator’s Update (which is actually an upgrade) bit me big time. And, it took me a week to recover from the mess MS dealt me.

It seems that many updates from Microsoft these days are poorly tested. Updates to a box are shoved down your throat whether you like it or not. The “new” Microsoft is repeating the lessons it should have learned with Windows Vista.

Thus, backups are more important now than ever!

Backups in Windows 10

Microsoft says that Windows 10 provides a “backup” through “file history.” But this is NOT a backup. A backup should include system files and partitions, not just your personal files. Windows 10 also provides the “Windows 7 backup”, but I really do not understand why it’s called that, and not “Windows 10 backup.” Is it going to be supported in the future? Maybe not!

Online Backups

An online backup is a good alternative. There are plenty of commercial products available, some of which were reviewed just a few days ago (http://www.pcmag.com/article2/0,2817,2288745,00.asp).  However, online backups have several problems: the services require a yearly subscription; there are limits on the size or duration of the backups saved; transferring data to and from the online service places demands on the internet link; online backup providers may not be reliable in the long run.

Backup to a spare PC on a LAN

Because I have a spare box, several large disks installed in it, with the machine attached to a gigabit LAN, I went with Acronis True Image 2017 to the spare box. The cost of Acronis was $60, which isn’t too bad. On each machine, a script is run to wake up the spare server, run Acronis True Image, then shutdown the server at the end of the backup.

The script is executed by the Windows Task Scheduler, which has the ability to wake up the computer from sleep mode, or start the process when the computer is first turned on. This script is written in Powershell. I would have preferred to write it in Bash, but WSL Bash does not work with the Windows Task Scheduler.

function grep {
    $input | out-string -stream | select-string $args
}

$var = tasklist | grep TrueImageHostNotify.exe

if ($var)
{
    'There is a backup currently running.'
    'Cannot start another backup because it might interfere with existing backup.'
    Exit 1
}
else
{
    'ok'
}

'No backups currently underway.'
' Turning on server...'

.\wol\wol\bin\Debug\netcoreapp1.1\win10-x64\publish\wol.exe -mac 44-8a-5b-ca-6b-be -mount \\llano\e\ -hostname llano

if ($?)
{
    'Backup server is now up.'
}
else
{
    'Wol of the backup server failed for some reason.'
    Exit 1
}


# Invoke backup. Note, the script name is generated by Acronis True Image when you create a backup plan.

&"c:\Program Files (x86)\Acronis\TrueImageHome\TrueImageLauncher.exe" "/script:C8E6E46F-2075-4916-9AE5-04977FDBA37F"
if ($?)
{
}
else
{
    'Acronis True Image launcher failed for some reason.'
    Exit 1
}

# Wait until the backup to begin.
while ($true)
{
    $var = tasklist.exe | grep TrueImageHomeNotify.exe
    if ($var)
    {
        break
    }
    'Waiting for backup to begin'
    Start-Sleep -s 5
}


# Wait for backup to finish.
while ($true)
{
    $var = tasklist.exe | grep TrueImageHomeNotify.exe
    if ($var)
    {
    }
    else
    {
        break
    }
    'Waiting for backup to end'
    Start-Sleep -s 5
}


# Turn off the remote backup server.

'Turning off server...'
$uid = Get-Content .\uid.txt -Raw
$pw = Get-Content .\pw.txt -Raw
.\psshutdown.exe -d '\\llano' -u $uid -p $pw

Exit 0

A simple WOL program in Net Core

To wake up the backup spare box, I wrote a “WOL” program in C# Net Core. It sends out a UDP “magic packet” to wake up the backup server when needed, which must be configured in both the BIOS and the network controller driver to accept WOL packets.

namespace WOL
{
    using System.IO;
    using System;
    using System.Collections.Generic;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    using System.Linq;
    using System.Runtime.InteropServices;

    internal class Program
    {
        private static int _count = 1;
        private static int _interval = 1;
        private static byte[] _mac;
        private static bool _sleep = false;
        private static string _mount;
        private static string _hostname;

        public static void Main(string[] args)
        {
            try
            {
                ParseArgs(args);
                if (_mac != null)
                {
                    List<byte> result = new List<byte>();
                    result.AddRange(Enumerable.Repeat((byte) 0xff, 6));
                    result.AddRange(Enumerable.Repeat(_mac, 16).SelectMany(arr => arr));
                    byte[] buffer = result.ToArray();
                    IPEndPoint ep = new IPEndPoint(IPAddress.Broadcast, 9);
                    UdpClient client = new UdpClient();
                    client.EnableBroadcast = true;
                    while (_count > 0)
                    {
                        Console.WriteLine("Sending packet...");
                        client.SendAsync(buffer, buffer.Length, _hostname, 9);
                        if (_count > 1)
                        {
                            Thread.Sleep(_interval * 1000);
                        }
                        _count--;
                    }
                }

                if (_mount != null) Mount(_mount);

                if (_sleep) Sleeper.Suspend();
            }
            catch (MacException e)
            {
                Console.WriteLine(e.Message);
                Environment.Exit(1);
            }
            catch (UsageException)
            {
                Usage();
                Environment.Exit(1);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                Environment.Exit(1);
            }
        }

        private static void Mount(string path)
        {
            var x = Directory.GetDirectories(path);
            System.Console.WriteLine(path + " size = " + x.Length);
        }

        private static void Usage()
        {
            Console.WriteLine($@"wol - Wake-On-Lan command-line utility
-c <int>              Number of packets to send to slave machine.
-i <int>              Number of seconds between packets sent.
-mac <string>         MAC address of the slave machine to wake up.
-mount <string>       UNC of the path of a slave file system to check if accessible.
-hostname <string>    Name of the slave computer on the LAN.
-s                    Indicates to place the host machine in sleep.
");
        }

        private static void ParseArgs(string[] args)
        {
            // Just in case, set up something.
            if (args.Length == 0)
            {
                throw new UsageException("No args. Check usage.");
            }
            int argc = 0;
            while (argc < args.Length)
            {
                if (args[argc] == "-mac")
                {
                    argc++;
                    string[] macParts = args[argc].Split(':', '-');
                    if (macParts.Length != 6)
                    {
                        throw new MacException(args[0]);
                    }

                    byte[] mac = new byte[6];
                    for (int i = 0; i < 6; i++)
                    {
                        mac[i] = Byte.Parse(macParts[i], System.Globalization.NumberStyles.HexNumber);
                    }

                    _mac = mac;
                    argc++;
                }
                else if (args[argc] == "-c")
                {
                    argc++;
                    _count = Int32.Parse(args[argc]);
                    argc++;
                }
                else if (args[argc] == "-i")
                {
                    argc++;
                    _interval = Int32.Parse(args[argc]);
                    argc++;
                }
                else if (args[argc] == "-s")
                {
                    argc++;
                    _sleep = true;
                }
                else if (args[argc] == "-mount")
                {
                    argc++;
                    _mount = args[argc];
                    argc++;
                }
                else if (args[argc] == "-hostname")
                {
                    argc++;
                    _hostname = args[argc];
                    argc++;
                }
                else
                    throw new UsageException("Unknown arg. Check command parameters.");
            }
        }
    }

    internal class MacException : ArgumentException
    {
        public MacException(string address)
            : base(address + " is not a valid MAC Address!")
        {
        }
    }
    internal class UsageException : ArgumentOutOfRangeException
    {
        public UsageException(string message)
            : base(message)
        {
        }
    }

    public class Sleeper
    {
        [DllImport("Powrprof.dll", SetLastError = true)]
        static extern bool SetSuspendState(bool hibernate, bool forceCritical, bool disableWakeEvent);

        public static void Suspend()
        {
            SetSuspendState(false, false, false);
        }
    }
}

 

Enjoy.