Backup Windows User Profiles Using Group Policy

This post is an evolution of my prior post here.

So I got a Synology NAS. Set up a rest-server docker container, and created a user profile backup script to be pushed out via group policy.

WINDOWS USER PROFILE BACKUP SCRIPT

@ECHO OFF
REM The following is the command to call this batch file from Task Scheduler for the "lock screen" or "login" event.
rem %COMSPEC% /c IF EXIST \\domain.local\netlogon\* ( start "Backup Agent" /min "\\domain.local\netlogon\restic-backup.bat" ) ^& exit
ECHO Setting environment variables, please wait...
SET RESTIC_REPOSITORY=rest:http://san:8000/repo
SET RESTIC_PASSWORD=ABCDEFGHIJKLMNOPQRSTUVWXYZ

REM Skip backup for administrators
IF /I %USERNAME% == administrator exit

REM Delete stale script lock file in case of prior interruption
del /f /s /q /a \\mss.ramlaw.local\ram-backup\.restic\logs\~%USERNAME%-%COMPUTERNAME%.LOCK

REM Determine if 32-bit or 64-bit OS
reg Query "HKLM\Hardware\Description\System\CentralProcessor\0" | find /i "x86" > NUL && set RESTIC_BIN=restic_x86.exe || set RESTIC_BIN=restic.exe

:START

REM Count .lock files created by current backups
FOR /f %%i in ('dir /b \\san\data\.restic\logs\*.lock ^| find /v /c "::"') do set LOCKS=%%i

CLS
ECHO Restic Backup Agent by Akrabu
ECHO.

REM Check for too many concurrent backups
IF %LOCKS% GEQ 6 (
ECHO Too many concurrent backups; waiting...

REM Generate random number from 1 to 300 and wait that many seconds
SET /A _RAND=%RANDOM% %% 300 + 1
TIMEOUT %_RAND%
GOTO START
)

REM Placeholder for a mandatory pause 
rem PING localhost -n 10 >NUL

REM Create lock file
ECHO. > \\san\data\.restic\logs\~%USERNAME%-%COMPUTERNAME%.LOCK

REM Write log header
ECHO. >>\\san\data\.restic\logs\%USERNAME%-%COMPUTERNAME%.LOG
ECHO ------------------------------------------------------- >>\\san\data\.restic\logs\%USERNAME%-%COMPUTERNAME%.LOG
ECHO. >>\\san\data\.restic\logs\%USERNAME%-%COMPUTERNAME%.LOG
ECHO STARTED: %DATE% %TIME% >>\\san\data\.restic\logs\%USERNAME%-%COMPUTERNAME%.LOG

REM Check if repository is locked and write to log
IF NOT EXIST \\san\data\.restic\unlocked (
 ECHO. >>\\san\data\.restic\logs\%USERNAME%-%COMPUTERNAME%.LOG
 ECHO REPOSITORY LOCKED OR UNAVAILABLE >>\\san\data\.restic\logs\%USERNAME%-%COMPUTERNAME%.LOG
)

REM Check for low bandwidth users
IF /I %USERNAME% == slowusername GOTO SLOW

REM Run backup, write output to log
ECHO Backing up user profile, please wait...
IF EXIST \\san\data\.restic\unlocked ( 
 \\san\data\.restic\%RESTIC_BIN% backup --cleanup-cache %USERPROFILE% %PROGRAMDATA% --tag USER_PROFILE --exclude-file=\\san\data\.restic\excludes.txt >>\\san\data\.restic\logs\%USERNAME%-%COMPUTERNAME%.LOG
 GOTO DONE
)

:SLOW

REM Run SLOW backup, write output to log
ECHO Backing up user profile, please wait...
IF EXIST \\san\data\.restic\unlocked ( 
 ECHO SLOW RUN: >>\\san\data\.restic\logs\%USERNAME%-%COMPUTERNAME%.LOG
 \\san\data\.restic\%RESTIC_BIN% backup --cleanup-cache %USERPROFILE% %PROGRAMDATA% --tag USER_PROFILE --exclude-file=\\san\data\.restic\excludes.txt --limit-upload=1024 >>\\san\data\.restic\logs\%USERNAME%-%COMPUTERNAME%.LOG
 ECHO. >>\\san\data\.restic\logs\%USERNAME%-%COMPUTERNAME%.LOG
 GOTO DONE
)

:DONE

REM Write log footer
ECHO. >>\\san\data\.restic\logs\%USERNAME%-%COMPUTERNAME%.LOG
ECHO STOPPED: %DATE% %TIME% >>\\san\data\.restic\logs\%USERNAME%-%COMPUTERNAME%.LOG

REM Delete lock file
del /f /s /q /a \\san\data\.restic\logs\~%USERNAME%-%COMPUTERNAME%.LOCK

:END

REM Done!
CLS
ECHO Restic Backup Agent by Akrabu
ECHO.
ECHO Backup complete!
ECHO.
ECHO ---

REM Loop commands
rem TIMEOUT 3600
rem GOTO START

EXIT

This will set the repo and password variables. IF the file “unlocked” exists and is accessible via the SAN, it will start restic in idle priority mode, backup the user profile, excluding a bunch of stuff (mostly browser caches), tag it with USER_PROFILE, clean up the cache if necessary, and append to a log file with their username. It also creates empty “STARTED” and “STOPPED” files in the LOG directory, to quickly see what times the script started and stopped. It will then wait one hour (3600 seconds), and do it all over again. If the “unlocked” file isn’t available, it will wait one hour and try again.

I have this script set to run as a GPO for all my domain users. The specific policy is under User Profile Backup Script (what I named it) > User Configuration > Policies > Administrative Templates > System > Logon > Run these programs at user logon. The specific command I use is cmd /c start "Backup Agent" /low /min "\\domain.local\netlogon\restic-backup.bat"

LOG FILE OUTPUT

------------------------------------------------------- 
 
STARTED: Mon 07/15/2019 17:57:34.00 

Files:         252 new,   387 changed,  9607 unmodified
Dirs:            0 new,     2 changed,     0 unmodified
Added to the repo: 120.910 MiB

processed 10246 files, 3.495 GiB in 0:53
snapshot d21c99df saved
 
STOPPED: Mon 07/15/2019 17:58:32.13 
 
------------------------------------------------------- 
 
STARTED: Tue 07/16/2019 11:50:20.12 

Files:         230 new,   430 changed,  9769 unmodified
Dirs:            0 new,     2 changed,     0 unmodified
Added to the repo: 121.821 MiB

processed 10429 files, 3.552 GiB in 0:35
snapshot 5aa9911e saved
 
STOPPED: Tue 07/16/2019 11:50:58.58 

SERVER BACKUP SCRIPT

@ECHO OFF
ECHO Starting SERVER_NAME backup...
paexec \\SERVER_NAME -u domain\administrator -p PASSWORD -e -c -f -d -background -csrc "\\san\data\.restic\restic.exe" restic.exe -r rest:http://san:8000/repo --password-file \\san\data\.restic\password.txt backup --cleanup-cache --exclude-file=\\san\data\.restic\excludes.txt --tag SERVER "E:\Shares" >>\\san\data\.restic\logs\!SERVER_NAME.LOG

Unfortunately I have not figured out how to log PAEXEC command output.

EXCLUDES FILE
AppData\Local\Microsoft\Windows\INetCookies\**
.ost
.tmp
AppData\Local\Microsoft\Windows\INetCache\

AppData\Local\Microsoft\Internet Explorer\DOMStore\**
AppData\Local\Microsoft\Internet Explorer\imagestore\**
AppData\Local\Microsoft\Internet Explorer\imagestore\**
C:\Users\admin
C:\Users\administrator
Cache\**
Code Cache\**
Copitrak\LOGGING\**
CryptnetUrlCache\**
GPUCache\**
IconCache*.db
Media Cache\**
Temp\**
Temporary Internet Files\**
WER\ReportQueue\**
WebCache\**
cache\**
iconcache*.db
temp\**

MAINTENANCE SCRIPT (WORK-IN-PROGRESS)

@ECHO OFF
SET RESTIC_REPOSITORY=rest:http://san:8000/repo
SET RESTIC_PASSWORD=ABCDEFGHIJKLMNOPQRSTUVWXYZ

:START

IF EXIST \\san\data\.restic\unlocked ( 
	ECHO Waiting for backups to finish, please wait...
	del /f /s /q /a \\san\data\.restic\unlocked
	TIMEOUT 60
	GOTO START
)

TIMEOUT 28800
ECHO.
	
( dir /b /a "\\path\to\repository\LOCKS" | findstr . ) > nul && (
  	ECHO Unlocking repository, please wait...
	ECHO.
	restic unlock
	GOTO START
	ECHO.
)

ECHO Cleaning stale script locks, please wait...
del /f /s /a /q \\san\data\.restic\logs\*.lock
ECHO.

REM EXTRA COMMANDS GO HERE
rem restic tag --set 
rem restic forget 

ECHO Rebuilding index, please wait...
restic rebuild-index

ECHO Cleaning repository, please wait...
ECHO.
restic forget --keep-within 336h --keep-hourly 672 --keep-daily 90 --keep-weekly 26 --keep-monthly 12 --group-by host --cleanup-cache --prune
ECHO.
ECHO Checking repository integrity, please wait...
ECHO.
restic check
ECHO.

ECHO. > \\san\data\.restic\unlocked
pause

Right now I have it waiting 8 hours after removing the “unlocked” file and 15 minutes after running restic unlock. I have the repo shared mainly as a REST-SERVER, but have a READ-ONLY SMB share for syncing the repo to off-site storage, and for the IF EXIST \\path\to\repository\locks\* statement. I did that to protect against cryptolocker viruses. As with everything here, use at your own risk!

Ps. Using rest-server instead of a SMB share is WAY faster. Highly recommend it!

Pps. I was almost swayed by Synology’s “Active Backup” software - but ultimately it just didn’t give me the fine-grain control that restic does. If you need bare-metal recovery for all users, it’s worth looking into. I just want the user profiles, myself.

Update:

I’ve changed a lot of stuff in the Backup script! For starters, you now have options to skip backups for certain users. You can check for too many concurrent users, and end the backup (or you could add a TIMEOUT 300 and GOTO :START to loop). It creates “.LOCK” files so you can see what is actually running, and not just what Restic thinks is running (so you know it’s safe to restic unlock before doing maintenance). There’s a global “unlocked” file you can create or delete to start/stop ALL future backups. The log files include date and time stamps. Finally, the script can check for low-bandwidth users and apply --limit-upload as necessary.

Hope it helps someone! I have it running every time someone’s screen automatically locks (using Group Policy). So far it’s working great!

Ps. If there’s a better way to post these scripts without formatting, someone please let me know lol

Updated batch file to check for x86 or x64 architecture and use the corresponding restic binary. The binary must be named restic.exe for x64 and restic_x86.exe for x86.

Lowered the number of concurrent backups due to bandwidth constraints. Set a random “retry” loop to wait for an open backup slot.

Made the script cleanup stale “script locks” in the beginning, so the “concurrent backup” check would be more useful. Theoretically, before, if enough people had stale locks, it would never run. Now it should clean up after itself the next time it runs.