28 Commits
1.3 ... 1.6

Author SHA1 Message Date
Kevin Woley
be5e8ead2b Release 1.6 Merge (#73)
* $SnapshotDeepMaintenanceDays = $null disables deep data checks

* updated install binary links to v0.14

* add config point for additional backup parameters

* - backup and maintenance run independently
- fixed issues with several incorrect function return values

* check for new version of restic during maintenance

* removing duplicate excludes, resolves #60

* update restic .exe version
add self-update to the install script

* Release 1.6 Changelog updates
2023-01-14 22:44:11 -08:00
Kevin Woley
c949cdde59 wording and style changes to the README 2021-11-14 20:05:59 -08:00
Felix Knecht
6d98ee03d8 Document how to get backup to sftp running (#52) 2021-11-14 20:01:46 -08:00
Kevin Woley
73142af934 added instructions for setting the Powershell execution policy 2021-09-12 20:07:30 -07:00
Kevin Woley
37426fa0fc Merge pull request #49 from ianneub/fix-url
Fix URLs for 64bit setup
2021-09-12 19:55:23 -07:00
Ian Neubert
8cf4190c8f Fix URLs for 64bit setup 2021-09-11 11:11:27 -07:00
Kevin Woley
5aebe71dab type: Restic 0.12.1, not 12.1 2021-09-11 06:04:27 -07:00
Kevin Woley
99cce23b42 minor spelling, typo fixes 2021-09-11 05:48:09 -07:00
Kevin Woley
a0180a6a4d added external, removable drive features 2021-09-11 05:44:06 -07:00
Kevin Woley
4c796b92b8 minor typos corrected 2021-09-11 05:43:58 -07:00
Kevin Woley
bd40ae1d5f Merge pull request #47 from kmwoley/release_1_5
Release 1.5
2021-09-11 05:26:53 -07:00
Kevin Woley
48d0ab73b2 update changelog for 1.5 release 2021-09-11 05:25:19 -07:00
Kevin Woley
fac2462981 Add the ability to $IgnoreMissingBackupSources
- when $true, missing external drives or folders don't produce errors
- when $null or $false, missing drives and/or folders result in an error
- Default is set ot $false as not to silently fail backing up a source
2021-09-10 21:51:01 -07:00
Kevin Woley
d448db94c0 add support for external, removable drive backup
- select backup source by drive label, device name, or serial number
- fix forget policy to be safe for multiple drives (group by host,tags)
- tag each backup source with drive/source name
2021-09-08 16:35:59 -07:00
Kevin Woley
79976f5019 update version of restic to 12.1 2021-09-08 16:08:38 -07:00
Kevin Woley
e826c326b6 Merge pull request #42 from kmwoley/release_1_4_1
Release 1.4.1
2021-05-09 21:02:56 -07:00
Kevin Woley
58558a6a67 Update CHANGELOG.md
1.4.1 release notes.
2021-05-09 21:01:28 -07:00
Kevin Woley
0919914dac internet connection test fix for PowerShell 7.1
resolves #37
2021-05-09 20:35:06 -07:00
Kevin Woley
ca90934e51 improve URL parsing, allow disabling of internet check
fixes #38
2021-05-09 20:19:29 -07:00
Kevin Woley
e5cc051edf remove uneeded -replace parameter 2021-05-07 20:06:55 -07:00
tree3887
817e67c354 Update backup.ps1 (#36)
Adds double quotes around each path and removes a trailing backslash from them as well. I have found through experimentation that restic does not like a backslash followed by a double quote. The backslash appears to be an escape character.
Surrounding the path with double quotes allows paths with spaces to be used.
2021-05-07 19:47:59 -07:00
Kevin Woley
d7cc581e6f Merge pull request #35 from tensberg/restic-0.12
Update installer to install restic 0.12.0
2021-02-27 16:02:52 -08:00
Michael Koch
f0c357520e Update installer to install restic 0.12.0
This fixes the backup script's usage of features not available in the
previous version.
2021-02-27 12:26:40 +01:00
Kevin Woley
f7c1ba32d2 update changelog for 1.4 2021-02-24 21:55:04 -08:00
Kevin Woley
117aa66430 Merge pull request #33 from kmwoley/release_1_4
Release 1.4
2021-02-24 21:45:09 -08:00
Kevin Woley
01a38f893a add the ability to set a pruning policy
defaulted to --max-unused 1% in configuration.
2021-02-24 21:02:41 -08:00
Kevin Woley
a840f5ae04 default retention polciy to group-by host
Fixes the issues from previous backup's having changing directory names, which cause snapshot retention inflation.
2021-02-24 19:59:40 -08:00
Kevin Woley
387390759d removed snapshot code, added --use-fs-snapshot
Related changes -

removed folder enumeration and instead point to the root filesystem:
snapshot handling will be better now that changing folders won't group sets

default exlusion list updates:
C:\$Recycle.Bin
C:\$WINDOWS.~BT
C:\$WinREAgent
2021-02-24 19:47:35 -08:00
7 changed files with 469 additions and 150 deletions

3
.gitignore vendored
View File

@@ -2,4 +2,5 @@ local.exclude
logs
restic.exe
secrets.ps1
state.xml
state.xml
testing

View File

@@ -1,14 +1,84 @@
# Changelog
## [1.3](https://github.com/kmwoley/restic-windows-backup/tree/HEAD)
## [1.6](https://github.com/kmwoley/restic-windows-backup/tree/1.6) (2023-01-14)
[Full Changelog](https://github.com/kmwoley/restic-windows-backup/compare/1.5...1.6)
[Full Changelog](https://github.com/kmwoley/restic-windows-backup/compare/1.2.1...HEAD)
Separated backup and maintenance execution loops, including sending separate emails for backup and maintenance reports. This allows for a maintenance failure not to cause a backup to be re-run, and vice-versa. This makes failures take a shorter time to resolve.
Logfiles now are formated as `*.backup.log.txt` and `*.maintenance.log.txt`
## Fixes
- Fixed issue #60, removing duplicate exclude lines
- Fixed several errors where functions would return incorrect success/failure results due to PowerShell's return value semantics
## Enhancements
- Updated installer to download v 0.15.0
- Installer will 'self-update' the Restic binary
- Maintenance will 'self-update' the Restic binary
- Added a configuration point for extra / additional parameters to be passed to the backup command (`$AdditionalBackupParameters`)
## [1.5](https://github.com/kmwoley/restic-windows-backup/tree/1.5) (2021-09-11)
[Full Changelog](https://github.com/kmwoley/restic-windows-backup/compare/1.4.1...1.5)
Added support for backing up removable drives (i.e. external USB disks). It's now possible to define a backup source by it's Volume label, device Serial Number, or the hardware Name.
**WARNING** If you have been previously backing up multiple drives, the default `forget` policy was likely pruning backup sets too aggressively and could lead to data loss. You **must** update your `$SnapshotRetentionPolicy` to include `@("--group-by", "host,tags", ...` to avoid pruning an entire drive's contents inadvertently!
## Fixes
- Updated default snapshot forget/prune retention policy to group by "host,tags" to prevent major data loss. Only configurations with multiple `$BackupSources` are impacted by this change.
- Added tags to each backup source to support grouping by tags. For existing backup sets, this change will result in a slightly longer backup the first time this updated script is run.
## Enhancements
- External, removable disk drives (i.e. USB hard drives) can be identified by their Volume Label, Serial Number, or Device Name. For example, if you have an external device with the Volume Label "MY BOOK", you can define a backup source as `$BackupSources["MY BOOK"]`. I would recommend using the device serial number to identify external drives to backup, which you can find using the Powershell `get-disk` command.
- Add the ability to $IgnoreMissingBackupSources. To make sure that errors are not thrown if the device is not present, there is now an option to ignore error reporting when a folder and entire backup source are missing. When `$true`, missing external drives or folders don't produce errors. When `$null` or `$false`, missing drives and/or folders result in an error. The default is set to `$false` as not to silently fail backing up a source.
- Updated install script to download Restic 0.12.1
## [1.4.1](https://github.com/kmwoley/restic-windows-backup/tree/1.4.1) (2021-05-29)
[Full Changelog](https://github.com/kmwoley/restic-windows-backup/compare/1.4...1.4.1)
Bugfix release.
## Fixes
- Improved URL parsing so that the internet connectivity check works if the URL doesn't provide a protocol
- Add PowerShell 7.1 support to internet connectivity check
## Enhancements
- Setting $InternetTestAttempts to 0 will now bypass the internet connectivity checks entirely
## [1.4](https://github.com/kmwoley/restic-windows-backup/tree/1.4) (2021-02-24)
[Full Changelog](https://github.com/kmwoley/restic-windows-backup/compare/1.3...1.4)
Moved to using Restic's inbuilt filesystem shadow copy creation (VSS).
## Breaking Change
`local.exclude` file changes that previously referenced the `resticVSS` directory will need to be changed to `C:\` or the relevant root drive letter.
## Other enhancements
- Future snapshot grouping (and cleanup) will be better since the root-level folders included in the backup won't change (instead, the script targets the root drive letter instead of a list of folders under the drive letter).
- Added the ability to set prune parameters via `.\config.ps1`, and defaulted the settings to `--group-by host` to clean up the aforementioned snapshot grouping & pruning.
- Updated the `windows.exclude` to include additional directories (most notably, the Recycle Bin is no longer backed up)
**Closed issues:**
- Remove VSS Operations, Switch to `--use-fs-snapshot` [\#32](https://github.com/kmwoley/restic-windows-backup/issues/32)
- powershell execution policy is blocking the scheduled task [\#27](https://github.com/kmwoley/restic-windows-backup/issues/27)
- VSS Cleanup Upon Errors [\#8](https://github.com/kmwoley/restic-windows-backup/issues/8)
**Merged pull requests:**
- Release 1.4 [\#33](https://github.com/kmwoley/restic-windows-backup/pull/33) ([kmwoley](https://github.com/kmwoley))
## [1.3](https://github.com/kmwoley/restic-windows-backup/tree/1.3) (2021-02-23)
[Full Changelog](https://github.com/kmwoley/restic-windows-backup/compare/1.2.1...1.3)
Improvements for Restic 0.12 and additional error logging.
**Closed issues:**
- backup errors after update to restic 0.12.0 due to --quiet and --verbose being used simultaneously [\#29](https://github.com/kmwoley/restic-windows-backup/issues/29)
- Restic + rclone errors [\#26](https://github.com/kmwoley/restic-windows-backup/issues/26)
- E-Mail sending errors are not logged [\#25](https://github.com/kmwoley/restic-windows-backup/issues/25)
- FYI: Restic now has built-in VSS support [\#23](https://github.com/kmwoley/restic-windows-backup/issues/23)
- SFTP backup [\#22](https://github.com/kmwoley/restic-windows-backup/issues/22)
- Dirrectory/Folder Backup [\#21](https://github.com/kmwoley/restic-windows-backup/issues/21)
@@ -16,6 +86,12 @@ Improvements for Restic 0.12 and additional error logging.
- Filtering out errors before deciding to retry ? [\#19](https://github.com/kmwoley/restic-windows-backup/issues/19)
- Backup task stucked [\#18](https://github.com/kmwoley/restic-windows-backup/issues/18)
**Merged pull requests:**
- Release 1.4 [\#31](https://github.com/kmwoley/restic-windows-backup/pull/31) ([kmwoley](https://github.com/kmwoley))
- Add '-ExecutionPolicy Bypass' to the task scheduler arguments to avoi… [\#28](https://github.com/kmwoley/restic-windows-backup/pull/28) ([scelfo](https://github.com/scelfo))
- Fix URI parsing [\#24](https://github.com/kmwoley/restic-windows-backup/pull/24) ([Phlogi](https://github.com/Phlogi))
## [1.2.1](https://github.com/kmwoley/restic-windows-backup/tree/1.2.1) (2020-06-08)
[Full Changelog](https://github.com/kmwoley/restic-windows-backup/compare/1.1...1.2.1)

View File

@@ -4,6 +4,7 @@ Simplifies the process of installation and running daily backups.
# Features
* **VSS (Volume Snapshot Service) support** - backup everything, don't worry about what files are open/in-use
* **Removable, External Drives** - drives can be identified by their volume labels or serial numbers, making it easy to backup drives that occasionally aren't there or change drive letter.
* **Easy Installation** - `install.ps1` script downloads Restic, initializes the restic repository, and setups up a Windows Task Scheduler task to run the backup daily
* **Backup, Maintenance and Monitoring are Automated** - `backup.ps1` script handles
* Emailing the results of each execution, including log files when there are problems
@@ -19,7 +20,8 @@ Simplifies the process of installation and running daily backups.
1. Download scripts from https://github.com/kmwoley/restic-windows-backup, and unzip them into `C:\restic`
1. Launch PowerShell as Administrator
1. Change your working directory to `C:\restic`
1. If you downloaded the files as a ZIP file, you may have to 'unblock' the execution of the scripts by running `Unblock-File *.ps1`
1. If you haven't done so in the past, set your Powershell script [execution policy](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.1) to allow for scripts to run. For example, `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned` is a good default.
1. Depending on the policy you choose, may need to 'unblock' the execution of the scripts you download by running `Unblock-File *.ps1`
1. Create `secrets.ps1` file
1. The secrets file contains location and passwords for your restic repository.
1. `secrets_template.ps1` is a template for the `secrets.ps1` file - copy or rename this file to `secrets.ps1` and edit.
@@ -28,6 +30,9 @@ Simplifies the process of installation and running daily backups.
1. Run `install.ps1` file
1. From the elevated (Run as Administrator) Powershell window, run `.\install.ps1`
1. This will initialize the repro, create your logfile directory, and create a scheduled task in Windows Task Scheduler to run the task daily.
1. Add your `$BackupSources` to `config.ps1`
1. By default, all of `C:\` will be backed up. You can add multiple root drives to be backed up. And you can define only specific folders you would like backed up.
1. External, removalbe disk drives (i.e. USB hard drives) can be identified by their Volume Label, Serial Number, or Device Name. For example, if you have an external device with the Volume Label "MY BOOK", you can define a backup source as `$BackupSources["MY BOOK"]=@()`. I would recommend using the device serial number to identify external drives to backup, which you can find using the Powershell `get-disk` command. You may also want to set `$IgnoreMissingBackupSources=$true` to avoid seeing errors when the removable drive is not present.
1. Add files/paths not to backup to `local.exclude`
1. If you don't want to modify the included exclude file, you can add any files/paths you want to exclude from the backup to `local.exclude`
1. Add `restic.exe` to the Windows Defender / Virus & Threat Detection Exclude list
@@ -43,5 +48,21 @@ Simplifies the process of installation and running daily backups.
1. `& $ResticExe find -i "*filename*"`
1. `& $ResticExe restore ...`
## Backup over SFTP
You can use any restic repository type you like pretty easily. SFTP on Windows, however, can be particularly tricky given that these scripts execute as the SYSTEM user and need to have access to the .ssh keys. Here are some steps and tips to getting it working.
1. Install as above. Your repository should be created properly. Tasked backups will fail for now though. This is because the `install.ps1` file is executed with your user, whereas the tasked backup will run as SYSTEM, which does not have any ssh config yet.
1. Open Task Scheduler and make sure the restic task is not running anymore by checking the active tasks
1. Edit `config.ps1` and turn off the internet connection test: `$InternetTestAttempts = 0` as the test does not recognize sftp addresses correctly
1. Copy the .ssh directory content from `%USERPROFILE%\.ssh` to `%WINDIR%\System32\config\systemprofile\.ssh` (This is the ssh config the SYSTEM account uses)
1. If you use a private key to access the sftp services it also needs to be in this directory. ssh checks the permissions though, so they need to be changed as well:
1. *Right click your key > Properties > Security > Advanced*
1. Change the owner to SYSTEM
1. *Disable inheritance* and keep the permissions
1. Remove all principals except SYSTEM and the Administrators group
This should get you up and running. If not, download [PsExec](https://docs.microsoft.com/en-us/sysinternals/downloads/psexec), start a powershell as admin user and run `.\PsExec.exe -s -i powershell.exe`. In this shell you will be the system user and you can try things out. See what `ssh user@server` says or try `cd C:\restic\; . .\config.ps1; . .\secrets.ps1; & $ResticExe check` (If you get lock errors, remember to check the Task Scheduler for any running restic instances in the background)
# Feedback?
Feel free to open issues or create PRs!

View File

@@ -17,6 +17,53 @@ $Script:ResticStateRepositoryInitialized = $null
$Script:ResticStateLastMaintenance = $null
$Script:ResticStateLastDeepMaintenance = $null
$Script:ResticStateMaintenanceCounter = $null
$Script:ResticStateLastBackupSuccessful = $true
$Script:ResticStateLastMaintenanceSuccessful = $true
# Returns all drive letters which exactly match the serial number, drive label, or drive name of
# the input parameter. Returns all drives if no input parameter is provided.
# inspiration: https://stackoverflow.com/questions/31088930/combine-get-disk-info-and-logicaldisk-info-in-powershell
function Get-Drives {
Param($ID)
foreach($disk in Get-CimInstance Win32_Diskdrive) {
$diskMetadata = Get-Disk | Where-Object { $_.Number -eq $disk.Index } | Select-Object -First 1
$partitions = Get-CimAssociatedInstance -ResultClassName Win32_DiskPartition -InputObject $disk
foreach($partition in $partitions) {
$drives = Get-CimAssociatedInstance -ResultClassName Win32_LogicalDisk -InputObject $partition
foreach($drive in $drives) {
$volume = Get-Volume |
Where-Object { $_.DriveLetter -eq $drive.DeviceID.Trim(":") } |
Select-Object -First 1
if(($diskMetadata.SerialNumber.trim() -eq $ID) -or
($disk.Caption -eq $ID) -or
($volume.FileSystemLabel -eq $ID) -or
($null -eq $ID)) {
[PSCustomObject] @{
DriveLetter = $drive.DeviceID
Number = $disk.Index
Label = $volume.FileSystemLabel
Manufacturer = $diskMetadata.Manufacturer
Model = $diskMetadata.Model
SerialNumber = $diskMetadata.SerialNumber.trim()
Name = $disk.Caption
FileSystem = $volume.FileSystem
PartitionKind = $diskMetadata.PartitionStyle
Drive = $drive
Partition = $partition
Disk = $disk
}
}
}
}
}
}
# restore backup state from disk
function Get-BackupState {
@@ -35,20 +82,20 @@ function Invoke-Unlock {
$locks = & $ResticExe list locks --no-lock -q 3>&1 2>> $ErrorLog
if($locks.Length -gt 0) {
# unlock the repository (assumes this machine is the only one that will ever use it)
& $ResticExe unlock 3>&1 2>> $ErrorLog | Tee-Object -Append $SuccessLog
Write-Output "[[Unlock]] Repository was locked. Unlocking. Past script failure?" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog
& $ResticExe unlock 3>&1 2>> $ErrorLog | Out-File -Append $SuccessLog
"[[Unlock]] Repository was locked. Unlocking." | Tee-Object -Append $ErrorLog | Out-File -Append $SuccessLog
Start-Sleep 120
}
}
# run maintenance on the backup set
function Invoke-Maintenance {
# test if maintenance on the backup set is needed. Return $true if maintenance is needed
function Test-Maintenance {
Param($SuccessLog, $ErrorLog)
# skip maintenance if disabled
if($SnapshotMaintenanceEnabled -eq $false) {
Write-Output "[[Maintenance]] Skipped - maintenance disabled" | Tee-Object -Append $SuccessLog
return
"[[Maintenance]] Skipping - maintenance disabled" | Out-File -Append $SuccessLog
return $false
}
# skip maintenance if it's been done recently
@@ -56,46 +103,59 @@ function Invoke-Maintenance {
$Script:ResticStateMaintenanceCounter += 1
$delta = New-TimeSpan -Start $ResticStateLastMaintenance -End $(Get-Date)
if(($delta.Days -lt $SnapshotMaintenanceDays) -and ($ResticStateMaintenanceCounter -lt $SnapshotMaintenanceInterval)) {
Write-Output "[[Maintenance]] Skipped - last maintenance $ResticStateLastMaintenance ($($delta.Days) days, $ResticStateMaintenanceCounter backups ago)" | Tee-Object -Append $SuccessLog
return
"[[Maintenance]] Skipping - last maintenance $ResticStateLastMaintenance ($($delta.Days) days, $ResticStateMaintenanceCounter backups ago)" | Out-File -Append $SuccessLog
return $false
}
else {
"[[Maintenance]] Running - last maintenance $ResticStateLastMaintenance ($($delta.Days) days, $ResticStateMaintenanceCounter backups ago)" | Out-File -Append $SuccessLog
return $true
}
}
else {
"[[Maintenance]] Running - no past maintenance history known." | Out-File -Append $SuccessLog
return $true
}
}
Write-Output "[[Maintenance]] Start $(Get-Date)" | Tee-Object -Append $SuccessLog
# run maintenance on the backup set
function Invoke-Maintenance {
Param($SuccessLog, $ErrorLog)
"[[Maintenance]] Start $(Get-Date)" | Out-File -Append $SuccessLog
$maintenance_success = $true
Start-Sleep 120
# forget snapshots based upon the retention policy
Write-Output "[[Maintenance]] Start forgetting..." | Tee-Object -Append $SuccessLog
& $ResticExe forget $SnapshotRetentionPolicy 3>&1 2>> $ErrorLog | Tee-Object -Append $SuccessLog
"[[Maintenance]] Start forgetting..." | Out-File -Append $SuccessLog
& $ResticExe forget $SnapshotRetentionPolicy 3>&1 2>> $ErrorLog | Out-File -Append $SuccessLog
if(-not $?) {
Write-Output "[[Maintenance]] Forget operation completed with errors" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog
"[[Maintenance]] Forget operation completed with errors" | Tee-Object -Append $ErrorLog | Out-File -Append $SuccessLog
$maintenance_success = $false
}
# prune (remove) data from the backup step. Running this separate from `forget` because
# `forget` only prunes when it detects removed snapshots upon invocation, not previously removed
Write-Output "[[Maintenance]] Start pruning..." | Tee-Object -Append $SuccessLog
& $ResticExe prune 3>&1 2>> $ErrorLog | Tee-Object -Append $SuccessLog
"[[Maintenance]] Start pruning..." | Out-File -Append $SuccessLog
& $ResticExe prune $SnapshotPrunePolicy 3>&1 2>> $ErrorLog | Out-File -Append $SuccessLog
if(-not $?) {
Write-Output "[[Maintenance]] Prune operation completed with errors" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog
"[[Maintenance]] Prune operation completed with errors" | Tee-Object -Append $ErrorLog | Out-File -Append $SuccessLog
$maintenance_success = $false
}
# check data to ensure consistency
Write-Output "[[Maintenance]] Start checking..." | Tee-Object -Append $SuccessLog
"[[Maintenance]] Start checking..." | Out-File -Append $SuccessLog
# check to determine if we want to do a full data check or not
$data_check = @()
if($null -ne $ResticStateLastDeepMaintenance) {
$delta = New-TimeSpan -Start $ResticStateLastDeepMaintenance -End $(Get-Date)
if($delta.Days -ge $SnapshotDeepMaintenanceDays) {
Write-Output "[[Maintenance]] Performing full data check - deep '--read-data' check last ran $ResticStateLastDeepMaintenance ($($delta.Days) days ago)" | Tee-Object -Append $SuccessLog
if(($null -ne $SnapshotDeepMaintenanceDays) -and ($delta.Days -ge $SnapshotDeepMaintenanceDays)) {
"[[Maintenance]] Performing read data check. Last '--read-data' check ran $ResticStateLastDeepMaintenance ($($delta.Days) days ago)" | Out-File -Append $SuccessLog
$data_check = @("--read-data")
$Script:ResticStateLastDeepMaintenance = Get-Date
}
else {
Write-Output "[[Maintenance]] Performing fast data check - deep '--read-data' check last ran $ResticStateLastDeepMaintenance ($($delta.Days) days ago)" | Tee-Object -Append $SuccessLog
"[[Maintenance]] Performing fast check. Last '--read-data' check ran $ResticStateLastDeepMaintenance ($($delta.Days) days ago)" | Out-File -Append $SuccessLog
}
}
else {
@@ -103,107 +163,164 @@ function Invoke-Maintenance {
$Script:ResticStateLastDeepMaintenance = Get-Date
}
& $ResticExe check @data_check 3>&1 2>> $ErrorLog | Tee-Object -Append $SuccessLog
& $ResticExe check @data_check 3>&1 2>> $ErrorLog | Out-File -Append $SuccessLog
if(-not $?) {
Write-Output "[[Maintenance]] Check completed with errors" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog
"[[Maintenance]] Check completed with errors" | Tee-Object -Append $ErrorLog | Out-File -Append $SuccessLog
$maintenance_success = $false
}
Write-Output "[[Maintenance]] End $(Get-Date)" | Tee-Object -Append $SuccessLog
# check for updated restic version
"[[Maintenance]] Checking for new version of restic..." | Out-File -Append $SuccessLog
& $ResticExe self-update 3>&1 2>> $ErrorLog | Out-File -Append $SuccessLog
if(-not $?) {
"[[Maintenance]] Self-update of restic.exe completed with errors" | Tee-Object -Append $ErrorLog | Out-File -Append $SuccessLog
$maintenance_success = $false
}
"[[Maintenance]] End $(Get-Date)" | Out-File -Append $SuccessLog
if($maintenance_success -eq $true) {
$Script:ResticStateLastMaintenance = Get-Date
$Script:ResticStateMaintenanceCounter = 0
}
return $maintenance_success
}
# Run restic backup
function Invoke-Backup {
Param($SuccessLog, $ErrorLog)
Write-Output "[[Backup]] Start $(Get-Date)" | Tee-Object -Append $SuccessLog
"[[Backup]] Start $(Get-Date)" | Out-File -Append $SuccessLog
$return_value = $true
$drive_count = $BackupSources.Count
$starting_location = Get-Location
ForEach ($item in $BackupSources.GetEnumerator()) {
$ShadowPath = Join-Path $item.Key 'resticVSS'
# Get the source drive letter or identifier and set as the root path
$root_path = $item.Key
$tag = $item.Key
# check for existance of previous, orphaned VSS directory (and remove it) before creating the shadow copy
if(Test-Path $ShadowPath) {
Write-Output "[[Backup]] VSS directory exists: '$ShadowPath' - removing. Past script failure?" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog
cmd /c rmdir $ShadowPath
}
# Create the shadow copy
$s1 = (Get-WmiObject -List Win32_ShadowCopy).Create($item.Key, "ClientAccessible")
$s2 = Get-WmiObject -Class Win32_ShadowCopy | Where-Object { $_.ID -eq $s1.ShadowID }
# Create a symbolic link to the shadow copy
$device = $s2.DeviceObject + "\"
cmd /c mklink /d $ShadowPath "$device" 3>&1 2>> $ErrorLog | Tee-Object -Append $SuccessLog
$vss_option = "--use-fs-snapshot"
# Build the new list of folders
$root_path = $ShadowPath
if($drive_count -eq 1) {
$root_path = "."
Set-Location $ShadowPath
# Test if root path is a valid path, if not assume it is an external drive identifier
if(-not (Test-Path $root_path)) {
# attempt to find a drive letter associated with the identifier provided
$drives = Get-Drives $root_path
if($drives.Count -gt 1) {
"[[Backup]] Fatal error - external drives with more than one partition are not currently supported." | Tee-Object -Append $SuccessLog | Out-File -Append $ErrorLog
$return_value = $false
continue
}
elseif ($drives.Count -eq 0) {
$ignore_error = ($null -ne $IgnoreMissingBackupSources) -and $IgnoreMissingBackupSources
$warning_message = "[[Backup]] Warning - backup path $root_path not found."
if($ignore_error) {
$warning_message | Out-File -Append $SuccessLog
}
else {
$warning_message | Tee-Object -Append $SuccessLog | Out-File -Append $ErrorLog
$return_value = $false
}
continue
}
$root_path = Join-Path $drives[0].DriveLetter ""
# disable VSS / file system snapshot for external drives
# TODO: would be best to just test for VSS compatibility on the drive, rather than assume it won't work
$vss_option = $null
}
"[[Backup]] Start $(Get-Date) [$tag]" | Out-File -Append $SuccessLog
# build the list of folders to backup
$folder_list = New-Object System.Collections.Generic.List[System.Object]
ForEach ($path in $item.Value) {
$p = Join-Path $root_path $path
$folder_list.Add($p)
if ($item.Value.Count -eq 0) {
# backup everything in the root if no folders are provided
$folder_list.Add($root_path)
}
else {
# Build the list of folders from settings
ForEach ($path in $item.Value) {
$p = '"{0}"' -f ((Join-Path $root_path $path) -replace "\\$")
if(Test-Path ($p -replace '"')) {
# add the folder if it exists
$folder_list.Add($p)
}
else {
# if the folder doesn't exist, log a warning/error
$ignore_error = ($null -ne $IgnoreMissingBackupSources) -and $IgnoreMissingBackupSources
$warning_message = "[[Backup]] Warning - backup path $p not found."
if($ignore_error) {
$warning_message | Out-File -Append $SuccessLog
}
else {
$warning_message | Tee-Object -Append $SuccessLog | Out-File -Append $ErrorLog
$return_value = $false
}
}
}
# backup everything in the root if no folders are provided
# note this won't select items with hidden attributes (a good thing to avoid)
if (-not $folder_list) {
ForEach ($path in Get-ChildItem $ShadowPath) {
$p = Join-Path $root_path $path
$folder_list.Add($p)
}
if(-not $folder_list) {
# there are no folders to backup
$ignore_error = ($null -ne $IgnoreMissingBackupSources) -and $IgnoreMissingBackupSources
$warning_message = "[[Backup]] Warning - no folders to back up!"
if($ignore_error) {
$warning_message | Out-File -Append $SuccessLog
}
else {
$warning_message | Tee-Object -Append $SuccessLog | Out-File -Append $ErrorLog
$return_value = $false
}
}
else {
# Launch Restic
& $ResticExe backup $folder_list $vss_option --tag "$tag" --exclude-file=$WindowsExcludeFile --exclude-file=$LocalExcludeFile $AdditionalBackupParameters 3>&1 2>> $ErrorLog | Out-File -Append $SuccessLog
if(-not $?) {
"[[Backup]] Completed with errors" | Tee-Object -Append $ErrorLog | Out-File -Append $SuccessLog
$return_value = $false
}
}
# Launch Restic
& $ResticExe backup $folder_list --exclude-file=$WindowsExcludeFile --exclude-file=$LocalExcludeFile 3>&1 2>> $ErrorLog | Tee-Object -Append $SuccessLog
if(-not $?) {
Write-Output "[[Backup]] Completed with errors" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog
$return_value = $false
}
# Delete the shadow copy and remove the symbolic link
if($drive_count -eq 1) {
Set-Location $starting_location
}
$s2.Delete()
cmd /c rmdir $ShadowPath
Write-Output "[[Backup]] End $(Get-Date)" | Tee-Object -Append $SuccessLog
"[[Backup]] End $(Get-Date) [$tag]" | Out-File -Append $SuccessLog
}
Set-Location $starting_location
"[[Backup]] End $(Get-Date)" | Out-File -Append $SuccessLog
return $return_value
}
function Send-Email {
Param($SuccessLog, $ErrorLog)
Param($SuccessLog, $ErrorLog, $Action)
# default the action string to "Backup"
if($null -eq $Action) {
$Action = "Backup"
}
$password = ConvertTo-SecureString $ResticEmailPassword -AsPlainText -Force
$credentials = New-Object System.Management.Automation.PSCredential ($ResticEmailUsername, $password)
$status = "SUCCESS"
$success_after_failure = $false
$past_failure = $false
$body = ""
if (($null -ne $SuccessLog) -and (Test-Path $SuccessLog) -and (Get-Item $SuccessLog).Length -gt 0) {
$body = $(Get-Content -Raw $SuccessLog)
# if previous run contained an error, send the success email confirming that the error has been resolved
# (i.e. get previous error log, if it's not empty, trigger the send of the success-after-failure email)
$previous_error_log = Get-ChildItem $LogPath -Filter '*err.txt' | Sort-Object -Descending LastWriteTime | Select-Object -Skip 1 | Select-Object -First 1
if(($null -ne $previous_error_log) -and ($previous_error_log.Length -gt 0)){
$success_after_failure = $true
# if previous run contained an error, send the success email confirming that the error has been resolved
if($Action -eq "Backup") {
$past_failure = -not $Script:ResticStateLastBackupSuccessful
}
else {
$past_failure = -not $Script:ResticStateLastMaintenanceSuccessful
}
}
else {
$body = "Crtical Error! Restic backup log is empty or missing. Check log file path."
$body = "Crtical Error! Restic $Action log is empty or missing. Check log file path."
$status = "ERROR"
}
$attachments = @{}
@@ -211,8 +328,8 @@ function Send-Email {
$attachments = @{Attachments = $ErrorLog}
$status = "ERROR"
}
if((($status -eq "SUCCESS") -and ($SendEmailOnSuccess -ne $false)) -or ((($status -eq "ERROR") -or $success_after_failure) -and ($SendEmailOnError -ne $false))) {
$subject = "$env:COMPUTERNAME Restic Backup Report [$status]"
if((($status -eq "SUCCESS") -and ($SendEmailOnSuccess -ne $false)) -or ((($status -eq "ERROR") -or $past_failure) -and ($SendEmailOnError -ne $false))) {
$subject = "$env:COMPUTERNAME Restic $Action Report [$status]"
# create a temporary error log to log errors; can't write to the same file that Send-MailMessage is reading
$temp_error_log = $ErrorLog + "_temp"
@@ -220,7 +337,7 @@ function Send-Email {
Send-MailMessage @ResticEmailConfig -From $ResticEmailFrom -To $ResticEmailTo -Credential $credentials -Subject $subject -Body $body @attachments 3>&1 2>> $temp_error_log
if(-not $?) {
Write-Output "[[Email]] Sending email completed with errors" | Tee-Object -Append $temp_error_log | Tee-Object -Append $SuccessLog
"[[Email]] Sending email completed with errors" | Tee-Object -Append $temp_error_log | Out-File -Append $SuccessLog
}
# join error logs and remove the temporary
@@ -231,9 +348,15 @@ function Send-Email {
function Invoke-ConnectivityCheck {
Param($SuccessLog, $ErrorLog)
if($InternetTestAttempts -le 0) {
"[[Internet]] Internet connectivity check disabled. Skipping." | Out-File -Append $SuccessLog
return $true
}
# skip the internet connectivity check for local repos
if(Test-Path $env:RESTIC_REPOSITORY) {
Write-Output "[[Internet]] Skipping internet connectivity check." | Tee-Object -Append $SuccessLog
"[[Internet]] Local repository. Skipping internet connectivity check." | Out-File -Append $SuccessLog
return $true
}
@@ -255,13 +378,17 @@ function Invoke-ConnectivityCheck {
}
else {
# parse connection string for hostname
# Uri parser doesn't handle leading connection type info (s3:, sftp:, rest:)
# Uri parser doesn't handle leading connection type info (s3:, sftp:, rest:)
$connection_string = $env:RESTIC_REPOSITORY -replace "^s3:" -replace "^sftp:" -replace "^rest:"
$repository_host = ([System.Uri]$connection_string).host
if(-not ($connection_string -match "://")) {
# Uri parser expects to have a protocol. Add 'https://' to make it parse correctly.
$connection_string = "https://" + $connection_string
}
$repository_host = ([System.Uri]$connection_string).DnsSafeHost
}
if([string]::IsNullOrEmpty($repository_host)) {
Write-Output "[[Internet]] Repository string could not be parsed." | Tee-Object -Append $SuccessLog | Tee-Object -Append $ErrorLog
"[[Internet]] Repository string could not be parsed." | Tee-Object -Append $SuccessLog | Out-File -Append $ErrorLog
return $false
}
@@ -271,15 +398,15 @@ function Invoke-ConnectivityCheck {
while($true) {
$connections = Get-NetRoute | Where-Object DestinationPrefix -eq '0.0.0.0/0' | Get-NetIPInterface | Where-Object ConnectionState -eq 'Connected' | Measure-Object | ForEach-Object{$_.Count}
if($sleep_count -le 0) {
Write-Output "[[Internet]] Connection to repository ($repository_host) could not be established." | Tee-Object -Append $SuccessLog | Tee-Object -Append $ErrorLog
"[[Internet]] Connection to repository ($repository_host) could not be established." | Tee-Object -Append $SuccessLog | Out-File -Append $ErrorLog
return $false
}
if(($null -eq $connections) -or ($connections -eq 0)) {
Write-Output "[[Internet]] Waiting for internet connectivity... $sleep_count" | Tee-Object -Append $SuccessLog
"[[Internet]] Waiting for internet connectivity... $sleep_count" | Out-File -Append $SuccessLog
Start-Sleep 30
}
elseif(!(Test-Connection -Server $repository_host -Quiet)) {
Write-Output "[[Internet]] Waiting for connection to repository ($repository_host)... $sleep_count" | Tee-Object -Append $SuccessLog
elseif(!(Test-Connection -ComputerName $repository_host -Quiet)) {
"[[Internet]] Waiting for connection to repository ($repository_host)... $sleep_count" | Out-File -Append $SuccessLog
Start-Sleep 30
}
else {
@@ -291,11 +418,18 @@ function Invoke-ConnectivityCheck {
# check previous logs
function Invoke-HistoryCheck {
Param($SuccessLog, $ErrorLog)
$logs = Get-ChildItem $LogPath -Filter '*err.txt' | ForEach-Object{$_.Length -gt 0}
Param($SuccessLog, $ErrorLog, $Action)
# default the action to "Backup"
if($null -eq $Action) {
$Action = "Backup"
}
$filter = "*$Action.err.txt".ToLower()
$logs = Get-ChildItem $LogPath -Filter $filter | ForEach-Object{$_.Length -gt 0}
$logs_with_success = ($logs | Where-Object {($_ -eq $false)}).Count
if($logs.Count -gt 0) {
Write-Output "[[History]] Backup success rate: $logs_with_success / $($logs.Count) ($(($logs_with_success / $logs.Count).tostring("P")))" | Tee-Object -Append $SuccessLog
Write-Output "[[History]] $Action success rate: $logs_with_success / $($logs.Count) ($(($logs_with_success / $logs.Count).tostring("P")))" | Tee-Object -Append $SuccessLog
}
}
@@ -306,7 +440,7 @@ function Invoke-Main {
if (-not (New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))
{
Write-Error "[[Backup]] Elevation required (run as administrator). Exiting."
exit
exit 1
}
# initialize secrets
@@ -320,54 +454,135 @@ function Invoke-Main {
if(!(Test-Path $LogPath)) {
Write-Error "[[Backup]] Log file directory $LogPath does not exist. Exiting."
Send-Email
exit
exit 1
}
$error_count = 0;
$error_count = 0
$backup_success = $false
$maintenance_success = $false
$maintenance_needed = $false
$attempt_count = $GlobalRetryAttempts
while ($attempt_count -gt 0) {
# setup logfiles
$timestamp = Get-Date -Format FileDateTime
$success_log = Join-Path $LogPath ($timestamp + ".log.txt")
$error_log = Join-Path $LogPath ($timestamp + ".err.txt")
$success_log = Join-Path $LogPath ($timestamp + ".backup.log.txt")
$error_log = Join-Path $LogPath ($timestamp + ".backup.err.txt")
$internet_available = Invoke-ConnectivityCheck $success_log $error_log
if($internet_available -eq $true) {
$repository_available = Invoke-ConnectivityCheck $success_log $error_log
if($repository_available -eq $true) {
Invoke-Unlock $success_log $error_log
$backup_success = Invoke-Backup $success_log $error_log
if($backup_success) {
Invoke-Maintenance $success_log $error_log
}
if (!(Test-Path $error_log) -or ((Get-Item $error_log).Length -eq 0)) {
# successful with no errors; end
$total_attempts = $GlobalRetryAttempts - $attempt_count + 1
Write-Output "Succeeded after $total_attempts attempt(s)" | Tee-Object -Append $success_log
Invoke-HistoryCheck $success_log $error_log
Send-Email $success_log $error_log
break;
}
}
# NOTE: a previously locked repository will cause errors in the log; but backup would be 'successful'
# Removing this overly-aggressive test for backup success and relying upon Invoke-Backup to report on success/failure
# $backup_success = ($backup_success -eq $true) -and (!(Test-Path $error_log) -or ((Get-Item $error_log).Length -eq 0))
$total_attempts = $GlobalRetryAttempts - $attempt_count + 1
if($backup_success -eq $true) {
# successful backup
Write-Output "[[Backup]] Succeeded after $total_attempts attempt(s)" | Tee-Object -Append $success_log
Write-Output "[[General]] Errors found. Log: $error_log" | Tee-Object -Append $success_log | Tee-Object -Append $error_log
$error_count++
$attempt_count--
if($attempt_count -gt 0) {
Write-Output "[[Retry]] Sleeping for 15 min and then retrying..." | Tee-Object -Append $success_log
# test to see if maintenance is needed if the backup was successful
$maintenance_needed = Test-Maintenance $success_log $error_log
}
else {
Write-Output "[[Backup]] Ran with errors on attempt $total_attempts" | Tee-Object -Append $success_log | Tee-Object -Append $error_log
$error_count++
}
}
else {
Write-Output "[[Retry]] Retry limit has been reached. No more attempts to backup will be made." | Tee-Object -Append $success_log
Write-Output "[[Backup]] Failed - cannot access repository." | Tee-Object -Append $success_log | Tee-Object -Append $error_log
$error_count++
}
if($internet_available -eq $true) {
Invoke-HistoryCheck $success_log $error_log
Send-Email $success_log $error_log
$attempt_count--
# update logs prior to sending email
if($backup_success -eq $false) {
if($attempt_count -gt 0) {
Write-Output "[[Backup]] Sleeping for 15 min and then retrying..." | Tee-Object -Append $success_log
}
else {
Write-Output "[[Backup]] Retry limit has been reached. No more attempts to backup will be made." | Tee-Object -Append $success_log
}
}
if($attempt_count -gt 0) {
Invoke-HistoryCheck $success_log $error_log "Backup"
Send-Email $success_log $error_log "Backup"
# update the state of the last backup success or failure
$Script:ResticStateLastBackupSuccessful = $backup_success
# Save state to file
Set-BackupState
# loop exit/wait condition
if(($backup_success -eq $false) -and ($attempt_count -gt 0)) {
Start-Sleep (15*60)
}
else {
break
}
}
# only run maintenance if the backup was successful and maintenance is needed
$attempt_count = $GlobalRetryAttempts
while (($maintenance_needed -eq $true) -and ($attempt_count -gt 0)) {
# setup logfiles
$timestamp = Get-Date -Format FileDateTime
$success_log = Join-Path $LogPath ($timestamp + ".maintenance.log.txt")
$error_log = Join-Path $LogPath ($timestamp + ".maintenance.err.txt")
$repository_available = Invoke-ConnectivityCheck $success_log $error_log
if($repository_available -eq $true) {
$maintenance_success = Invoke-Maintenance $success_log $error_log
# $maintenance_success = ($maintenance_success -eq $true) -and (!(Test-Path $error_log) -or ((Get-Item $error_log).Length -eq 0))
$total_attempts = $GlobalRetryAttempts - $attempt_count + 1
if($maintenance_success -eq $true) {
Write-Output "[[Maintenance]] Succeeded after $total_attempts attempt(s)" | Tee-Object -Append $success_log
}
else {
Write-Output "[[Maintenance]] Ran with errors on attempt $total_attempts" | Tee-Object -Append $success_log | Tee-Object -Append $error_log
$error_count++
}
}
else {
Write-Output "[[Maintenance]] Failed - cannot access repository." | Tee-Object -Append $success_log | Tee-Object -Append $error_log
$error_count++
}
$attempt_count--
# update logs prior to sending email
if($maintenance_success -eq $false) {
if($attempt_count -gt 0) {
Write-Output "[[Maintenance]] Sleeping for 15 min and then retrying..." | Tee-Object -Append $success_log
}
else {
Write-Output "[[Maintenance]] Retry limit has been reached. No more attempts to run maintenance will be made." | Tee-Object -Append $success_log
}
}
Invoke-HistoryCheck $success_log $error_log "Maintenance"
Send-Email $success_log $error_log "Maintenance"
# update the state of the last maintenance success or failure
$Script:ResticStateLastMaintenanceSuccessful = $maintenance_success
# Save state to file
Set-BackupState
# loop exit/wait condition
if(($maintenance_success -eq $false) -and ($attempt_count -gt 0)) {
Start-Sleep (15*60)
}
else {
break
}
}
# Save state to file
Set-BackupState
# cleanup older log files

View File

@@ -9,10 +9,13 @@ $LogPath = Join-Path $InstallPath "logs"
$LogRetentionDays = 30
$InternetTestAttempts = 10
$GlobalRetryAttempts = 4
$IgnoreMissingBackupSources = $false
$AdditionalBackupParameters = @("--exclude-if-present", ".nobackup")
# maintenance configuration
$SnapshotMaintenanceEnabled = $true
$SnapshotRetentionPolicy = @("--keep-daily", "30", "--keep-weekly", "52", "--keep-monthly", "24", "--keep-yearly", "10")
$SnapshotRetentionPolicy = @("--group-by", "host,tags", "--keep-daily", "30", "--keep-weekly", "52", "--keep-monthly", "24", "--keep-yearly", "10")
$SnapshotPrunePolicy = @("--max-unused", "1%")
$SnapshotMaintenanceInterval = 7
$SnapshotMaintenanceDays = 30
$SnapshotDeepMaintenanceDays = 90;
@@ -29,3 +32,6 @@ $BackupSources["C:\"] = @(
#$BackupSources["D:\"] = @(
# 'Software'
#)
#$BackupSources["DRIVE_LABEL_NAME_OR_SERIAL_NUMBER"] = @(
# 'FolderName'
#)

View File

@@ -5,10 +5,10 @@
if(-not (Test-Path $ResticExe)) {
$url = $null
if([Environment]::Is64BitOperatingSystem){
$url = "https://github.com/restic/restic/releases/download/v0.9.6/restic_0.9.6_windows_amd64.zip"
$url = "https://github.com/restic/restic/releases/download/v0.15.0/restic_0.15.0_windows_amd64.zip"
}
else {
$url = "https://github.com/restic/restic/releases/download/v0.9.6/restic_0.9.6_windows_386.zip"
$url = "https://github.com/restic/restic/releases/download/v0.15.0/restic_0.15.0_windows_386.zip"
}
$output = Join-Path $InstallPath "restic.zip"
Invoke-WebRequest -Uri $url -OutFile $output
@@ -17,6 +17,8 @@ if(-not (Test-Path $ResticExe)) {
Get-ChildItem *.exe | Rename-Item -NewName $ExeName
}
# Invoke restic self-update to check for a newer version
& $ResticExe self-update
# Create log directory if it doesn't exit
if(-not (Test-Path $LogPath)) {

View File

@@ -1,23 +1,25 @@
# default excludes
# examples https://github.com/duplicati/duplicati/blob/master/Duplicati/Library/Utility/FilterGroups.cs
# note, because we're using a VSS directory, we can use that as the root dir for exclude rules (i.e. resticVSS)
resticVSS\hiberfil.sys
resticVSS\pagefile.sys
resticVSS\swapfile.sys
resticVSS\$Recycle.Bin
resticVSS\autoexec.bat
resticVSS\Config.Msi
resticVSS\Documents and Settings
resticVSS\Recycled
resticVSS\Recycler
resticVSS\System Volume Information
resticVSS\Recovery
resticVSS\Program Files
resticVSS\Program Files (x86)
resticVSS\ProgramData
resticVSS\PerfLogs
resticVSS\Windows
resticVSS\Windows.old
C:\hiberfil.sys
C:\pagefile.sys
C:\swapfile.sys
C:\$Recycle.Bin
C:\autoexec.bat
C:\Config.Msi
C:\Documents and Settings
C:\Recycled
C:\Recycler
C:\$$Recycle.Bin
C:\System Volume Information
C:\Recovery
C:\Program Files
C:\Program Files (x86)
C:\ProgramData
C:\PerfLogs
C:\Windows
C:\Windows.old
C:\$$WINDOWS.~BT
C:\$$WinREAgent
Microsoft\Windows\Recent
Microsoft\**\RecoveryStore*
Microsoft\**\Windows\*.edb
@@ -32,7 +34,7 @@ UsrClass.dat
Dropbox
AppData\Local\Google\Drive
Google Drive\.tmp.drivedownload
resticVSS\OneDriveTemp
C:\OneDriveTemp
# browsers
Google\Chrome
@@ -45,10 +47,6 @@ AppData\Local\ConnectedDevicesPlatform
AppData\Local\Packages
AppData\Roaming\Signal
AppData\Local\ElevatedDiagnostics
AppData\Local\Microsoft\Windows\Explorer
AppData\Local\Microsoft\Windows\INetCache
AppData\Local\Microsoft\Windows\WebCache
AppData\Local\Microsoft\Windows Store
AppData\Local\restic
AppData\LocalLow\Microsoft\CryptnetUrlCache
AppData\Local\IsolatedStorage