114 Commits
1.0 ... main

Author SHA1 Message Date
Kevin Woley
e9241c0744 Update CHANGELOG.md with 1.8 release notes 2025-02-20 21:59:09 -08:00
Kevin Woley
649d8dc119 Merge pull request #116 from kmwoley/release_1.8
Release 1.8
2025-02-20 21:42:14 -08:00
Kevin Woley
7ba3db056a comment cleanup in update.ps1 2025-02-20 21:41:03 -08:00
Kevin Woley
6244dbc910 remove testing string 2025-02-20 15:34:50 -08:00
Kevin Woley
033bdb3afd update to error strings in try/catch blocks 2025-02-20 15:32:55 -08:00
Kevin Woley
d7bc684e33 minor update to error strings 2025-02-20 15:30:26 -08:00
Kevin Woley
ce170f0685 bugfix: error checking was broken by release 1.7.1 (c8776b42c0) 2025-02-20 15:27:39 -08:00
Kevin Woley
efd7c78f59 default the installation location to the current working directory if not running as a script 2025-02-19 21:21:25 -08:00
Kevin Woley
463d1c9d42 update README with update.ps1 instructions 2025-02-19 21:14:33 -08:00
Kevin Woley
1af63b87e2 add additional usage instructions to update.ps1 2025-02-16 17:38:18 -08:00
Kevin Woley
71b7bd1407 adding 'update.ps1' which can be used to update the installed scripts from a branch or the latest release 2025-02-16 00:40:20 -08:00
Kevin Woley
916c11d20f create a sample configuration file and remove the config.ps1 from the repo 2025-02-08 21:18:36 -08:00
Kevin Woley
51a35ef109 small typo fix in config example 2025-02-08 01:10:23 -08:00
Kevin Woley
a0de49b804 revert unintended config changes 2025-02-08 00:58:54 -08:00
Kevin Woley
f68ff68ae2 add examples to for CustomAction configuration 2025-02-08 00:57:07 -08:00
Kevin Woley
eaf97cd2d7 add custom actions configuration to enable invoking scripts other commands on script start and end. Defined by $CustomAction* config variables. 2025-02-08 00:29:06 -08:00
Kevin Woley
7c0111308a updated install script task schedule setup to set LogonType, resolves #40 2025-02-07 23:28:57 -08:00
Kevin Woley
ec212218c8 explicitly test the backup source media for VSS support instead of assuming it is or is not supported 2025-02-07 22:55:32 -08:00
Kevin Woley
bc2969514c Update CHANGELOG.md with 1.7.1 release notes 2025-02-03 21:07:09 -08:00
Kevin Woley
6f3c31b6cb Merge pull request #114 from kmwoley/release_1.7.1
Release 1.7.1
2025-02-03 20:52:09 -08:00
Kevin Woley
edef828984 Merge pull request #112 from innovara/fix_typos_release_1.7.1
Fix some typos on comments
2025-02-03 10:18:19 -08:00
Manuel Fombuena
19d8e65d92 fix some typos
Signed-off-by: Manuel Fombuena <fombuena@outlook.com>
2025-02-03 11:00:54 +00:00
Kevin Woley
d180f3f30c Metered connection check is now works for PowerShell 7, improved internet connection check logging 2025-02-01 22:48:06 -08:00
Kevin Woley
b642afa509 fix logic for checking metered network configuration settings 2025-01-27 15:28:23 -08:00
Kevin Woley
add848471f make $BackupOnMeteredNetwork default to true in config 2025-01-27 15:16:27 -08:00
Kevin Woley
8fed01a1f6 Merge pull request #108 from innovara/metered-check
Add feature to control backups on metered connections
2025-01-27 15:12:00 -08:00
Kevin Woley
21e68f4b42 Merge branch 'release_1.7.1' into metered-check 2025-01-27 15:09:09 -08:00
Kevin Woley
c75bc74724 added more verose and explicit logging to the console via Write-Host 2025-01-27 11:14:41 -08:00
Kevin Woley
80fab4092c remove $Global: scoped variables in favor of $Script: scope, and explicitly scope each usage 2025-01-27 10:26:45 -08:00
Kevin Woley
c433c44b70 updated install script to use the global parameters, removed separate $SelfUpdateParameters 2025-01-27 10:15:13 -08:00
Kevin Woley
c8776b42c0 replaced "&" execution with Invoke-Expression, enabled refactoring to set $GlobalParameters in one place
Removed $ResticExe, $StateFile, and $LogPath from config.ps1
2025-01-26 12:53:12 -08:00
Kevin Woley
33163ecb60 reorganize the layout/order of the config.ps1 file 2025-01-26 10:41:09 -08:00
Kevin Woley
7673c83e3f Merge pull request #96 from woelfisch/main
Add optional configuration options for additional parameters to resti…
2025-01-26 00:03:30 -08:00
Kevin Woley
c930b9499e Merge branch 'main' into main 2025-01-26 00:00:29 -08:00
Kevin Woley
3f3a87a04f Change log for 1.7 2025-01-25 17:44:13 -08:00
Kevin Woley
6ef3526716 Merge pull request #110 from kmwoley/2024.11
Merge 2024.11 into Main
2025-01-25 17:13:31 -08:00
Kevin Woley
8a8165fe01 cast email addresses to their Mimekit types 2025-01-25 17:04:19 -08:00
Kevin Woley
296621268e streamline the Send-MailKitMessage installation 2025-01-25 16:44:08 -08:00
Kevin Woley
4ee0eff191 rename email $PS* variable name settings to $Restic*, and warn if using deprecated email settings, 2025-01-24 23:34:43 -08:00
Kevin Woley
6e5bf4823b fix merged credential handling, handle back compat for existing secrets for SMTP port 2025-01-24 23:10:35 -08:00
innovara
c4a497e0d1 Replace deprecated Send-MailMessage with Send-MailKitMessage (#107)
The Send-MailMessage cmdlet is obsolete. It doesn't guarantee secure connections to SMTP servers.

Use Send-MailKitMessage module instead.

Signed-off-by: Manuel Fombuena <fombuena@outlook.com>
Co-authored-by: Kevin Woley <kmwoley@users.noreply.github.com>
2025-01-24 21:50:08 -08:00
Kevin Woley
b81aa242bc improve default configuration values 2024-11-14 12:15:14 -08:00
Manuel Fombuena
09b9066049 Add feature to control backups on metered connections
restic-windows-backup runs backups irrespective of the host being on a metered connection or unrestricted. That can lead to situations in which the backup takes place when the user is on a metered connection and would rather not do it at that time, either because they would incur costs or use up their data allowance.

The default value of $BackupOnMeteredNetwork is set to $false following a conservative approach.

Closes https://github.com/kmwoley/restic-windows-backup/issues/82

Signed-off-by: Manuel Fombuena <fombuena@outlook.com>
2024-11-11 22:10:00 +00:00
Kevin Woley
efcdc9d291 clean up variable naming, style conventions for previous PR 2024-11-10 23:41:08 -08:00
SeeJayEmm
aad279210a Allow for unauthenticated SMTP. (#81)
* Allow for unauthenticated SMTP, fixes #66
2024-11-10 23:26:31 -08:00
Kevin Woley
5a660ea5d6 powershell 7.3+ fixes 2024-11-10 23:14:46 -08:00
Daniel Harding
7609b8147a Limit snapshot pruning to the current host (#94)
* Limit snapshot pruning to the current host

Address #70 by only pruning snapshots from the current host by default when performing maintenance.  This avoids inadvertently pruning snapshots from other hosts when that might not be desired.

* add back 'host' to group-by

I think it's safer to keep `--group-by 'host,tags'` even if the `--host` parameter is provided. This makes sure that the `forget` always groups together snapshots from the same host. It may be unneeded.

---------

Co-authored-by: Kevin Woley <kmwoley@users.noreply.github.com>
2024-11-10 15:41:26 -08:00
Export33
b33d2d176b Added Example (#84)
Co-authored-by: Export <donotforgetme@web.de>
2024-11-10 15:16:18 -08:00
Export33
6ec929a75c fix typ (#83)
Co-authored-by: Export <donotforgetme@web.de>
2024-11-10 15:13:29 -08:00
Jonas Hagenberg
e06230c68b fix typo in readme (#74) 2024-11-10 14:52:32 -08:00
Kevin Woley
3a8fb017d7 adding the full path to restic to AV process exclusion 2024-11-09 11:23:57 -08:00
Kevin Woley
1456799955 add "Nextcloud" to the default windows exclude directory 2024-11-09 11:13:06 -08:00
Kevin Woley
df52535a17 update .gitignore to include restic.exe backup file 2024-11-09 11:12:37 -08:00
Kevin Woley
7f520e3995 update install script to restic version 0.17.3 2024-11-09 11:12:09 -08:00
Joerg Reuter
0e8262ab05 Add optional configuration options for additional parameters to restic calls, $AdditionalParameters and $SelfUpdateParameters and make self update of restic binary configurable via $AllowResticSelfUpdate config option 2024-03-17 16:26:51 +01:00
Kevin Woley
f759630532 removing harmless, unneeded ; 2023-01-14 23:20:34 -08:00
enzo-g
6b13a4b710 Update README.md (#58)
Correct one typo mistake
2023-01-14 23:01:05 -08:00
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
Kevin Woley
15d0fc8354 Merge pull request #31 from kmwoley/release_1_4
Release 1.4
2021-02-22 21:02:49 -08:00
Kevin Woley
cdba59be95 Update changelog 2021-02-22 21:01:20 -08:00
Kevin Woley
1eaef5f0c8 add error logging to sending emails
improve general error messaging
resolves #25
2021-02-22 20:34:53 -08:00
Kevin Woley
6bfba97a4c reverting the fix for URL parsing 2021-02-22 05:36:53 -08:00
Kevin Woley
ed92f4e213 Merge pull request #24 from Phlogi/patch-1
Fix URI parsing
2021-02-22 05:08:27 -08:00
Kevin Woley
48b5c61215 Merge pull request #28 from scelfo/master
Add '-ExecutionPolicy Bypass' to the task scheduler arguments to avoi…
2021-02-22 05:05:17 -08:00
Kevin Woley
49305c7632 Merge branch 'release_1_4' of https://github.com/kmwoley/restic-windows-backup into release_1_4 2021-02-22 05:02:18 -08:00
Kevin Woley
6227786ae2 remove conflicting verbose/quiet
resolves #29
2021-02-22 05:02:05 -08:00
Kevin Woley
eaaccc4c9e remove conflicting verbose/quiet
resolves #29
2021-02-22 04:28:20 -08:00
Tony Scelfo
471cf57b7e Add '-ExecutionPolicy Bypass' to the task scheduler arguments to avoid the issue described in https://github.com/kmwoley/restic-windows-backup/issues/27. 2021-01-08 14:17:34 -07:00
Phlogi
626ac2a0c5 Fix URI parsing
Tested only for sftp. Without the leading ..// the System.Uri will fail on the next line, as the string is empty.
2020-10-26 07:56:58 +01:00
Kevin Woley
eba7f4d10a Merge pull request #17 from kmwoley/release_1_3
Release 1 3
2020-06-08 09:43:22 -07:00
Kevin Woley
9dba4fd40b adding changelog
closes #15
2020-06-08 09:41:26 -07:00
Kevin Woley
229d21d753 add "generic" repo specific url internet tests 2020-06-08 08:39:07 -07:00
Kevin Woley
69e8a23b36 remove azure, gs, and b2 from connectivity check 2020-06-08 07:48:57 -07:00
Kevin Woley
63a8bb9218 retry error messaging improvements 2020-04-30 09:49:49 -07:00
Kevin Woley
7cff028471 send email on first success after a failure
(if SendEmailOnError is enabled)
2020-04-29 14:39:54 -07:00
Kevin Woley
102bc0ccc9 Add Zoom, VS Code, Signal, and Spotify to default exclude list 2020-04-28 20:58:36 -07:00
Kevin Woley
88bc571998 Add date to Release 1.2 2020-04-28 20:44:39 -07:00
Kevin Woley
7d7978f0cc Merge pull request #13 from kmwoley/option-refinement
1.2 Release
2020-04-28 20:42:49 -07:00
Kevin Woley
6edfe75696 Changelog update. 2020-04-28 20:41:08 -07:00
Kevin Woley
5ef9e3bb53 add 32-bit windows support to the install
Closes #7
2020-04-28 20:28:08 -07:00
Kevin Woley
b73008a5f1 internet connectivity check handles all repo types
Closes #10
2020-04-28 20:08:41 -07:00
Kevin Woley
be35fd5e7b Merge pull request #6 from kmwoley/option-refinement
add changelog
2020-02-15 09:40:55 -08:00
Kevin Woley
716e98563b typo fix 2020-02-15 09:39:53 -08:00
Kevin Woley
f8e9653aa9 add changelog 2020-02-15 09:38:41 -08:00
Kevin Woley
ab15f5387b Merge pull request #4 from kmwoley/option-refinement
add options to enable/disable email sending  and maintenance
2020-02-14 08:40:30 -08:00
Kevin Woley
0334de5546 bug fix: previous shadow copy could be backed up
If the previous run of the script were aborted, the shadow copy would be left aroudn and then backed up.
2020-02-13 23:04:46 -08:00
Kevin Woley
7f576135fc config points for disabling maintenance, email 2020-02-13 22:58:13 -08:00
10 changed files with 1324 additions and 219 deletions

5
.gitignore vendored
View File

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

203
CHANGELOG.md Normal file
View File

@@ -0,0 +1,203 @@
# Changelog
## [1.8](https://github.com/kmwoley/restic-windows-backup/tree/1.8) (2025-02-20)
[Full Changelog](https://github.com/kmwoley/restic-windows-backup/compare/1.7.1...1.8)
## Summary
* New features
* Added `update.ps1` which makes updating `restic-windows-backup` installations easier.
* Added the ability to run custom actions at the start and end of the script execution. Can be used to invoke healthchecks or run custom scripts. Look at `config_sample.ps1` for examples.
* Bug fixes
* Explicitly test the backup source media for VSS support instead of assuming it is or is not supported
* Install script sets Task Scheduler user LogonType correctly, fixing #40
* Error checking of restic.exe results fixed (was broken by release 1.7.1)
## [1.7.1](https://github.com/kmwoley/restic-windows-backup/tree/1.7.1) (2025-02-03)
[Full Changelog](https://github.com/kmwoley/restic-windows-backup/compare/1.7...1.7.1)
## Summary
* (Optionally) prevent backup & maintenance while on a metered network connection. By default, backups will occur while on a metered network connection. To disable backups over metered network connections, set `$BackupOnMeteredNetwork = $false` in `config.ps1`
* Added `$GlobalParameters = @()` configuration variable, which will apply additional configuration parameters every time `restic.exe` is run. This is useful to add options for different types of backend targets.
* Added `$SelfUpdateEnabled = $true` configuration variable, which can be used to disable `restic.exe` from automatically updating to the latest version when maintenance is run. To disable self update, set `$SelfUpdateEnabled = $false` in `config.ps1`
 
## What's Changed
* Add optional configuration options for additional parameters to resti… by @woelfisch in https://github.com/kmwoley/restic-windows-backup/pull/96
* Add feature to control backups on metered connections by @innovara in https://github.com/kmwoley/restic-windows-backup/pull/108
## New Contributors
* @woelfisch made their first contribution in https://github.com/kmwoley/restic-windows-backup/pull/96
## [1.7](https://github.com/kmwoley/restic-windows-backup/tree/1.7) (2025-01-25)
[Full Changelog](https://github.com/kmwoley/restic-windows-backup/compare/1.6...1.7)
*Upgrade Warning - Future Breaking Change*
This release deprecates the following `secrets.ps1` variables:
* `$PSEmailServer` is replaced by `$ResticEmailServer`
* `$ResticEmailConfig` is replaced by `$ResticEmailPort`
In the next release, `$ResticEmailServer` and `$ResticEmailPort` will be required. This release will still work if the deprecated variables are defined.
## What's Changed
* Update README.md by @enzo-g in https://github.com/kmwoley/restic-windows-backup/pull/58
* fix typo in readme by @jonas-hag in https://github.com/kmwoley/restic-windows-backup/pull/74
* fix typo by @Export33 in https://github.com/kmwoley/restic-windows-backup/pull/83
* Added a more detailed example for backup sources by @Export33 in https://github.com/kmwoley/restic-windows-backup/pull/84
* Limit snapshot pruning to the current host by @living180 in https://github.com/kmwoley/restic-windows-backup/pull/94
* Allow for unauthenticated SMTP. by @SeeJayEmm in https://github.com/kmwoley/restic-windows-backup/pull/81
* Replace deprecated Send-MailMessage with Send-MailKitMessage by @innovara in https://github.com/kmwoley/restic-windows-backup/pull/107
* Merge 2024.11 into Main by @kmwoley in https://github.com/kmwoley/restic-windows-backup/pull/110
## New Contributors
* @enzo-g made their first contribution in https://github.com/kmwoley/restic-windows-backup/pull/58
* @jonas-hag made their first contribution in https://github.com/kmwoley/restic-windows-backup/pull/74
* @Export33 made their first contribution in https://github.com/kmwoley/restic-windows-backup/pull/83
* @living180 made their first contribution in https://github.com/kmwoley/restic-windows-backup/pull/94
* @SeeJayEmm made their first contribution in https://github.com/kmwoley/restic-windows-backup/pull/81
* @innovara made their first contribution in https://github.com/kmwoley/restic-windows-backup/pull/107
## [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)
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)
- Docker format [\#20](https://github.com/kmwoley/restic-windows-backup/issues/20)
- 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)
* Internet connectivity test now supports more repository types (s3:, sftp:, rest:, azure:, gs:), and ignores unsupported (swift:, rclone: and local)
* Add 32-bit support in the `install.ps1`
* Fix/improve internet connectivity checks for azure: gs: b2:
**Closed issues:**
- azure repo could not be parsed [\#15](https://github.com/kmwoley/restic-windows-backup/issues/15)
- Need to strip rest: in addition to s3: from RESTIC\_REPOSITORY [\#14](https://github.com/kmwoley/restic-windows-backup/issues/14)
- Use non-s3 repos [\#10](https://github.com/kmwoley/restic-windows-backup/issues/10)
- Test-Connection fails [\#9](https://github.com/kmwoley/restic-windows-backup/issues/9)
- 32bit Windows Support [\#7](https://github.com/kmwoley/restic-windows-backup/issues/7)
- Add changelog [\#1](https://github.com/kmwoley/restic-windows-backup/issues/1)
**Merged pull requests:**
- Release 1 3 [\#17](https://github.com/kmwoley/restic-windows-backup/pull/17) ([kmwoley](https://github.com/kmwoley))
- 1.2 Release [\#13](https://github.com/kmwoley/restic-windows-backup/pull/13) ([kmwoley](https://github.com/kmwoley))
## [1.1](https://github.com/kmwoley/restic-windows-backup/tree/1.1) (2020-02-15)
[Full Changelog](https://github.com/kmwoley/restic-windows-backup/compare/1.0...1.1)
* Users can now set the following variables to control sending emails on success and/or error conditions.
* Users can now completely disable maintenance activities.
New `config.ps1` variable defaults for these options are:
```
$SnapshotMaintenanceEnabled = $true
$SendEmailOnSuccess = $false
$SendEmailOnError = $true
```
**Closed issues:**
- Ability to disable maintenance [\#3](https://github.com/kmwoley/restic-windows-backup/issues/3)
- Ability to disable mail sending [\#2](https://github.com/kmwoley/restic-windows-backup/issues/2)
**Merged pull requests:**
- add changelog [\#6](https://github.com/kmwoley/restic-windows-backup/pull/6) ([kmwoley](https://github.com/kmwoley))
- add options to enable/disable email sending and maintenance [\#4](https://github.com/kmwoley/restic-windows-backup/pull/4) ([kmwoley](https://github.com/kmwoley))
## [1.0](https://github.com/kmwoley/restic-windows-backup/tree/1.0) (2020-02-09)
[Full Changelog](https://github.com/kmwoley/restic-windows-backup/compare/34eae241aa1dcf08ed1d4d4f930e1d1a5bf5788a...1.0)
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*

View File

@@ -4,7 +4,9 @@ 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
* **Easy to update** - `update.ps1` script can be used to keep your scripts up to date with the latest release on GitHub
* **Backup, Maintenance and Monitoring are Automated** - `backup.ps1` script handles
* Emailing the results of each execution, including log files when there are problems
* Runs routine maintenence (pruning and checking the repo for errors on a regular basis)
@@ -12,27 +14,48 @@ Simplifies the process of installation and running daily backups.
# Installation Instructions
1. Create your restic repository
1. This is up to you to sort out where you want the data to go to. *Minio, B2, S3, oh my.*
1. Install Scripts
1. **Create your restic repository**
1. This is up to you to sort out where you want the data to go to. *Minio, B2, S3, etc.*. Refer to the restic documents about how to create your repository.
1. **Install the scripts**
1. Create script directory: `C:\restic`
1. Download scripts from https://github.com/kmwoley/restic-windows-backup, and unzip them into `C:\restic`
1. Download scripts using the `update.ps1` script.
1. Open PowerShell
1. Change your working directory to the installation directory
```
cd c:\restic
```
1. Run the `update.ps1` script:
```
Invoke-Expression (Invoke-WebRequest -Uri "https://raw.githubusercontent.com/kmwoley/restic-windows-backup/main/update.ps1" -UseBasicParsing).Content
```
*Alternatively, you can download the scripts from this repository and 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. 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.
1. restic will pick up the repo destination from the environment variables you set in this file - see this doc for more information about configuring restic repos https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html
1. Email sending configuration is also contained with this file. The scripts assume you want to get emails about the success/failure of each backup attempt.
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, this is a good default:
```
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
```
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. The secrets file contains location and passwords for your restic repository.
1. `secrets_sample.ps1` is an example of the `secrets.ps1` file. Copy or rename this file to `secrets.ps1` and edit.
1. Restic will pick up the repo destination from the environment variables you set in this file - see this doc for more information about configuring restic repos https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html
1. Email sending configuration is also contained with this file. The scripts are able to send email about the success/failure of each backup attempt.
1. Create `config.ps1` file. The config file contains the settings that control how the script runs backups, forgets snapshots, and prunes the restic repository. It's important that you configure this file to meet your needs since it will be backing up and maintaining your repository.
1. `config_sample.ps1` contins an example configuration file. Copy or rename this file to `config.ps1` and edit to suit your needs.
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, 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"]=@()`. It is recommended to use 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. Review all of the default settings in `config.ps1`.
1. Most of the defaults are safe, but you should be sure restic is configured to meet your specifics needs.
1. **Warning** - if you're using a shared restic repository across multiple machines, pay close attention to the `$SnapshotRetentionPolicy` settings to be sure this script does not intentionally destroy backup data in your repository.
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. This will initialize the repo, create your logfile directory, create a scheduled task in Windows Task Scheduler to run the task daily, and install Send-MailKitMessage module.
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
1. Add `C:\restic\restic.exe` to the Windows Defender / Virus & Threat Detection Exclude list
1. Backups on Windows are really slow if you don't set the Antivirus to ignore restic.
1. Navigate from the Start menu to: *Virus & threat protection > Manage Settings > Exclusions (Add or remove exclusions) > Add an exclusion (Process) > Process Name: "restic.exe"*
1. Navigate from the Start menu to: *Virus & threat protection > Manage Settings > Exclusions (Add or remove exclusions) > Add an exclusion (Process) > Process Name: "C:\restic\restic.exe"*
1. *(Recommended)* To a test backup triggered from Task Scheduler
1. It's recommended to open Windows Task Scheduler and trigger the task to run manually to test your first backup.
1. *Open Task Scheduler > Find "Restic Backup" > Right Click > Run*
@@ -43,5 +66,44 @@ Simplifies the process of installation and running daily backups.
1. `& $ResticExe find -i "*filename*"`
1. `& $ResticExe restore ...`
## Updating restic-windows-backup
Use `update.ps1` to update the installed `restic-windows-backup` scripts to the latest release.
1. Open PowerShell (no need to be Administrator)
1. Change directory to your installation directory (e.g. `c:\restic`)
1. Run `update.ps1`
### `update.ps1` Details
Running `update.ps1` without any parameters will check for a new release from `kmwoley/restic-windows-backup`. If there is a newer release, the script will overwrite the local files in the script directory with the updated scripts.
* The script will not overwrite your local configuration files (i.e. `config.ps1` or `secrets.ps1`).
* Any custom files created in the installation directory will not be deleted or modified (e.g. any custom action scripts, log files, etc.)
* The script will warn before overwriting any files that have been changed since the last installation.
* When `update.ps1` is run the first time, it will prompt before overwriting (since it may not know the current version of the fiels installed).
### `update.ps1` Options
* `-Mode <release | branch> (Default: release)` - change if the script updates from the latest release or a branch of `kmwoley/restic-windows-backup`
* `-Branch <branch> (Default: 'main')` - When in branch mode, this parameter controls which branch to install from. Defaults to the `main` branch.
* `-InstallPath <directory>` - choose which directory to install the files into. Defaults to the directory that `update.ps1` is in.
## 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!

674
backup.ps1 Normal file → Executable file
View File

@@ -2,94 +2,183 @@
# Restic Windows Backup Script
#
# =========== start configuration =========== #
# =========== start configuration =========== #
# set restic configuration parmeters (destination, passwords, etc.)
# load restic configuration parameters (destination, passwords, etc.)
$SecretsScript = Join-Path $PSScriptRoot "secrets.ps1"
# backup configuration variables
# load backup configuration variables
$ConfigScript = Join-Path $PSScriptRoot "config.ps1"
# =========== end configuration =========== #
# make LASTEXITCODE global to enable error checking for Invoke-Expression commands
$global:LASTEXITCODE=0
# globals for state storage
$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
}
}
}
}
}
}
# test the path's storage media for VSS support
# returns $true if VSS is supported at the provided path
function Test-VSSSupport {
Param($test_path)
$drive_letter = Split-Path $test_path -Qualifier
$volume = Get-WmiObject -Query "SELECT * FROM Win32_Volume WHERE DriveLetter = '$drive_letter'"
$deviceID = ($volume.DeviceID -replace '.*(\{.*\}).*', '$1')
### https://learn.microsoft.com/en-us/previous-versions/windows/desktop/vsswmi/win32-shadowvolumesupport
$supportedVolumes = Get-WmiObject -Query "SELECT * FROM Win32_ShadowVolumeSupport WHERE __PATH LIKE '%$deviceID%'"
return ($null -ne $supportedVolumes)
}
# restore backup state from disk
function Get-BackupState {
if(Test-Path $StateFile) {
Import-Clixml $StateFile | ForEach-Object{ Set-Variable -Scope Script $_.Name $_.Value }
if(Test-Path $Script:StateFile) {
Import-Clixml $Script:StateFile | ForEach-Object{ Set-Variable -Scope Script $_.Name $_.Value }
}
}
function Set-BackupState {
Get-Variable ResticState* | Export-Clixml $StateFile
Get-Variable ResticState* | Export-Clixml $Script:StateFile
}
# unlock the repository if need be
function Invoke-Unlock {
Param($SuccessLog, $ErrorLog)
$locks = & $ResticExe list locks --no-lock -q 3>&1 2>> $ErrorLog
$locks = Invoke-Expression "$Script:ResticExe list locks --no-lock -q 3>&1 2>> $ErrorLog"
if($LASTEXITCODE) {
"[[Unlock]] Warning: unable to list locks." | Tee-Object -Append $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
Start-Sleep 120
Invoke-Expression "$Script:ResticExe unlock 3>&1 2>> $ErrorLog | Out-File -Append $SuccessLog"
if($LASTEXITCODE) {
"[[Unlock]] Error - unable to unlock repository." | Tee-Object -Append $ErrorLog
}
"[[Unlock]] Repository was locked. Unlocking." | Tee-Object -Append $ErrorLog | Out-File -Append $SuccessLog
Start-Sleep 120
}
}
# 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) {
"[[Maintenance]] Skipping - maintenance disabled" | Out-File -Append $SuccessLog
return $false
}
# skip maintenance if it's been done recently
if(($null -ne $ResticStateLastMaintenance) -and ($null -ne $ResticStateMaintenanceCounter)) {
$Script:ResticStateMaintenanceCounter += 1
$delta = New-TimeSpan -Start $ResticStateLastMaintenance -End $(Get-Date)
if(($delta.Days -lt $SnapshotMaintenanceDays) -and ($ResticStateMaintenanceCounter -lt $SnapshotMaintenanceInterval)) {
"[[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
}
}
# run maintenance on the backup set
function Invoke-Maintenance {
Param($SuccessLog, $ErrorLog)
# skip maintenance if it's been done recently
if(($null -ne $ResticStateLastMaintenance) -and ($null -ne $ResticStateMaintenanceCounter)) {
$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
}
}
Write-Output "[[Maintenance]] Start $(Get-Date)" | Tee-Object -Append $SuccessLog
"[[Maintenance]] Start $(Get-Date)" | Tee-Object -Append $SuccessLog | Write-Host
$maintenance_success = $true
Start-Sleep 120
# forget snapshots based upon the retention policy
Write-Output "[[Maintenance]] Start forgetting..." | Tee-Object -Append $SuccessLog
& $ResticExe --verbose -q forget $SnapshotRetentionPolicy 3>&1 2>> $ErrorLog | Tee-Object -Append $SuccessLog
if(-not $?) {
Write-Output "[[Maintenance]] Forget operation completed with errors" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog
"[[Maintenance]] Start forgetting..." | Out-File -Append $SuccessLog
Invoke-Expression "$Script:ResticExe forget $SnapshotRetentionPolicy 3>&1 2>> $ErrorLog | Out-File -Append $SuccessLog"
if($LASTEXITCODE) {
"[[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 --verbose -q prune 3>&1 2>> $ErrorLog | Tee-Object -Append $SuccessLog
if(-not $?) {
Write-Output "[[Maintenance]] Prune operation completed with errors" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog
"[[Maintenance]] Start pruning..." | Out-File -Append $SuccessLog
Invoke-Expression "$Script:ResticExe prune $SnapshotPrunePolicy 3>&1 2>> $ErrorLog | Out-File -Append $SuccessLog"
if($LASTEXITCODE) {
"[[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 {
@@ -97,130 +186,318 @@ function Invoke-Maintenance {
$Script:ResticStateLastDeepMaintenance = Get-Date
}
& $ResticExe --verbose -q check @data_check 3>&1 2>> $ErrorLog | Tee-Object -Append $SuccessLog
if(-not $?) {
Write-Output "[[Maintenance]] Check completed with errors" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog
Invoke-Expression "$Script:ResticExe check $data_check 3>&1 2>> $ErrorLog | Out-File -Append $SuccessLog"
if($LASTEXITCODE) {
"[[Maintenance]] Data check completed with errors" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog | Write-Host
$maintenance_success = $false
}
Write-Output "[[Maintenance]] End $(Get-Date)" | Tee-Object -Append $SuccessLog
# Invoke restic self-update to check for a newer version
# This is enabled by default unless configuration disables self-update
if ([String]::IsNullOrEmpty($SelfUpdateEnabled) -or ($SelfUpdateEnabled -eq $true)) {
# check for updated restic version
"[[Maintenance]] Checking for new version of restic..." | Out-File -Append $SuccessLog
Invoke-Expression "$Script:ResticExe self-update 3>&1 2>> $ErrorLog | Out-File -Append $SuccessLog"
if($LASTEXITCODE) {
"[[Maintenance]] Self-update of restic.exe completed with errors" | Tee-Object -Append $ErrorLog | Out-File -Append $SuccessLog
$maintenance_success = $false
}
}
"[[Maintenance]] End $(Get-Date)" | Tee-Object -Append $SuccessLog | Write-Host
if($maintenance_success -eq $true) {
$Script:ResticStateLastMaintenance = Get-Date
$Script:ResticStateMaintenanceCounter = 0;
$Script:ResticStateMaintenanceCounter = 0
}
return $maintenance_success
}
# Run restic backup
# Run restic backup
function Invoke-Backup {
Param($SuccessLog, $ErrorLog)
Write-Output "[[Backup]] Start $(Get-Date)" | Tee-Object -Append $SuccessLog
"[[Backup]] Start $(Get-Date)" | Tee-Object -Append $SuccessLog | Write-Host
$return_value = $true
$drive_count = $BackupSources.Count
$starting_location = Get-Location
ForEach ($item in $BackupSources.GetEnumerator()) {
# 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 }
# Get the source drive letter or identifier and set as the root path
$root_path = $item.Key
$tag = $item.Key
$device = $s2.DeviceObject + "\"
$ShadowPath = Join-Path $item.Key 'resticVSS'
# 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
}
# there is exactly one drive
$root_path = Join-Path $drives[0].DriveLetter ""
}
# determine if VSS is supported by the drive
$vss_option = $null
if(Test-VSSSupport $root_path) {
$vss_option = "--use-fs-snapshot"
}
# Create a symbolic link to the shadow copy
cmd /c mklink /d $ShadowPath "$device" 3>&1 2>> $ErrorLog | Tee-Object -Append $SuccessLog
# Build the new list of folders
$root_path = $ShadowPath
if($drive_count -eq 1) {
$root_path = "."
Set-Location $ShadowPath
}
"[[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
Invoke-Expression "$Script:ResticExe backup $folder_list $vss_option --tag $tag --exclude-file=$WindowsExcludeFile --exclude-file=$LocalExcludeFile $AdditionalBackupParameters 3>&1 2>> $ErrorLog | Out-File -Append $SuccessLog"
if($LASTEXITCODE) {
"[[Backup]] Completed with errors" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog | Write-Host
$return_value = $false
}
}
# Launch Restic
& $ResticExe --verbose -q 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)" | Tee-Object -Append $SuccessLog | Write-Host
return $return_value
}
function Send-Email {
Param($SuccessLog, $ErrorLog)
$password = ConvertTo-SecureString $ResticEmailPassword -AsPlainText -Force
$credentials = New-Object System.Management.Automation.PSCredential ($ResticEmailUsername, $password)
Param($SuccessLog, $ErrorLog, $Action)
Import-Module Send-MailKitMessage
# default the action string to "Backup"
if($null -eq $Action) {
$Action = "Backup"
}
# set email credentials if a username and passsword are provided in configuration
$credentials = @{}
if (-not [String]::IsNullOrEmpty($ResticEmailPassword) -and -not [String]::IsNullOrEmpty($ResticEmailUsername)) {
$password = ConvertTo-SecureString -String $ResticEmailPassword -AsPlainText -Force
$credentials = @{
"Credential" = [System.Management.Automation.PSCredential]::new($ResticEmailUsername, $password)
}
}
# Backwards compatibility for $ResticEmailConfig port definition:
# $ResticEmailConfig is obsolete and should be replaced with $ResticEmailPort
if ($null -ne $ResticEmailConfig -and $ResticEmailConfig.ContainsKey('Port')) {
if ($null -eq $ResticEmailPort) {
$ResticEmailPort = $ResticEmailConfig['Port']
'[[Email]] Warning - $ResticEmailConfig is deprecated. Define $ResticEmailPort in secrets.ps1 instead.' | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog | Write-Host
}
}
# Backwards compatibility for $PSEmailServer rename to $ResticEmailServer
if (($null -ne $PSEmailServer) -and ($null -eq $ResticEmailServer)) {
$ResticEmailServer = $PSEmailServer
'[[Email]] Warning - $PSEmailServer is deprecated. Define $ResticEmailServer in secrets.ps1 instead.' | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog | Write-Host
}
$status = "SUCCESS"
$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
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 = "Critical Error! Restic $Action log is empty or missing. Check log file path."
$status = "ERROR"
}
$attachments = @{}
$attachments = [System.Collections.Generic.List[string]]::new()
if (($null -ne $ErrorLog) -and (Test-Path $ErrorLog) -and (Get-Item $ErrorLog).Length -gt 0) {
$attachments = @{Attachments = $ErrorLog}
$attachments.Add("$ErrorLog")
$status = "ERROR"
}
$subject = "$env:COMPUTERNAME Restic Backup Report [$status]"
Send-MailMessage @ResticEmailConfig -From $ResticEmailFrom -To $ResticEmailTo -Credential $credentials -Subject $subject -Body $body @attachments
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"
$from = [MimeKit.MailboxAddress]$ResticEmailFrom;
$recipients = [MimeKit.InternetAddressList]::new();
$recipients.Add([MimeKit.InternetAddress]$ResticEmailTo);
Send-MailKitMessage -SMTPServer $ResticEmailServer -Port $ResticEmailPort -UseSecureConnectionIfAvailable @credentials -From $from -RecipientList $recipients -Subject $subject -TextBody $body -AttachmentList $attachments 3>&1 2>> $temp_error_log | Out-File -Append $SuccessLog
if(-not $?) {
"[[Email]] Sending email completed with errors" | Tee-Object -Append $temp_error_log | Tee-Object -Append $SuccessLog | Write-Host
}
# join error logs and remove the temporary
Get-Content $temp_error_log | Add-Content $ErrorLog
Remove-Item $temp_error_log
}
}
# check if on metered network,
# returns $true the current connection is a metered network
function Invoke-MeteredCheck {
$scriptBlock = {
# load NetworkInformation class from the Windows Runtime (WinRT) environment
[void][Windows.Networking.Connectivity.NetworkInformation, Windows, ContentType = WindowsRuntime]
$cost = [Windows.Networking.Connectivity.NetworkInformation]::GetInternetConnectionProfile().GetConnectionCost()
return ($cost.ApproachingDataLimit -or $cost.OverDataLimit -or $cost.Roaming -or $cost.BackgroundDataUsageRestricted -or ($cost.NetworkCostType -ne 'Unrestricted'))
}
# run this check in PowerShell 5.1
# this is a workaround for lack of WinRT support in PowerShell 7
$result = powershell.exe -Version 5.1 -Command "$scriptBlock"
return ($result -ieq "True")
}
# check network conditions, retrying a limited number of times until a connection is established
# returns $true if the repository is accessible and the configuration allows us to use it
function Invoke-ConnectivityCheck {
Param($SuccessLog, $ErrorLog)
# parse connection string for hostname
# TODO: handle non-s3 repositories
# Uri parser doesn't handle leading connection type info
$connection_string = $env:RESTIC_REPOSITORY -replace "s3:"
$repository_host = ([System.Uri]$connection_string).host
$sleep_time = 30
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) {
"[[Internet]] Local repository. Skipping internet connectivity check." | Out-File -Append $SuccessLog
return $true
}
$repository_host = ''
# use generic internet service for non-specific repo types (e.g. swift:, rclone:, etc. )
if(($env:RESTIC_REPOSITORY -match "^swift:") -or
($env:RESTIC_REPOSITORY -match "^rclone:")) {
$repository_host = "cloudflare.com"
}
elseif($env:RESTIC_REPOSITORY -match "^b2:") {
$repository_host = "api.backblazeb2.com"
}
elseif($env:RESTIC_REPOSITORY -match "^azure:") {
$repository_host = "azure.microsoft.com"
}
elseif($env:RESTIC_REPOSITORY -match "^gs:") {
$repository_host = "storage.googleapis.com"
}
else {
# parse connection string for hostname
# Uri parser doesn't handle leading connection type info (s3:, sftp:, rest:)
$connection_string = $env:RESTIC_REPOSITORY -replace "^s3:" -replace "^sftp:" -replace "^rest:"
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)) {
"[[Internet]] Repository string could not be parsed." | Tee-Object -Append $SuccessLog | Out-File -Append $ErrorLog
return $false
}
# test for internet connectivity
$connections = 0
$sleep_count = $InternetTestAttempts
$restricted_by_metered_network = $false
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 could not be established." | Tee-Object -Append $SuccessLog | Tee-Object -Append $ErrorLog
if($restricted_by_metered_network) {
"[[Internet]] Connection to repository ($repository_host) is available but blocked by metered network." | Tee-Object -Append $SuccessLog | Out-File -Append $ErrorLog
}
else {
"[[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
Start-Sleep 30
"[[Internet]] Waiting $sleep_time seconds for internet connectivity... ($sleep_count/$InternetTestAttempts)" | Out-File -Append $SuccessLog
Start-Sleep $sleep_time
}
elseif(!(Test-Connection -Server $repository_host -Quiet)) {
Write-Output "[[Internet]] Waiting for connection to repository ($repository_host)... $sleep_count" | Tee-Object -Append $SuccessLog
Start-Sleep 30
elseif(!(Test-Connection -ComputerName $repository_host -Quiet)) {
"[[Internet]] Waiting $sleep_time seconds for connection to repository ($repository_host)... ($sleep_count/$InternetTestAttempts)" | Out-File -Append $SuccessLog
Start-Sleep $sleep_time
}
elseif((-not ([String]::IsNullOrEmpty($BackupOnMeteredNetwork) -or $BackupOnMeteredNetwork)) -and (Invoke-MeteredCheck)) {
"[[Internet]] Waiting $sleep_time seconds for an unmetered network connection... ($sleep_count/$InternetTestAttempts)" | Out-File -Append $SuccessLog
$restricted_by_metered_network = $true
Start-Sleep $sleep_time
}
else {
return $true
@@ -231,81 +508,204 @@ function Invoke-ConnectivityCheck {
# check previous logs
function Invoke-HistoryCheck {
Param($SuccessLog, $ErrorLog)
$logs = Get-ChildItem $LogPath -Filter '*err.txt' | %{$_.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 $Script: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
"[[History]] $Action success rate: $logs_with_success / $($logs.Count) ($(($logs_with_success / $logs.Count).tostring("P")))" | Tee-Object -Append $SuccessLog | Write-Host
}
}
# main function
function Invoke-Main {
# check for elevation, required for creation of shadow copy (VSS)
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
. $SecretsScript
# initialize config
. $ConfigScript
# apply global configuration
$Script:ResticExe = Join-Path $InstallPath $ExeName
if(-not [String]::IsNullOrEmpty($GlobalParameters)) {
$Script:ResticExe = "$Script:ResticExe $GlobalParameters"
}
$Script:StateFile = Join-Path $InstallPath "state.xml"
$Script:LogPath = Join-Path $InstallPath "logs"
Get-BackupState
if(!(Test-Path $LogPath)) {
Write-Error "[[Backup]] Log file directory $LogPath does not exist. Exiting."
if(!(Test-Path $Script:LogPath)) {
Write-Error "[[Backup]] Log file directory $Script:LogPath does not exist. Exiting."
Send-Email
exit
exit 1
}
$error_count = 0;
# custom start action
if($null -ne $CustomActionStart) {
Invoke-Expression $CustomActionStart
}
$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")
$internet_available = Invoke-ConnectivityCheck $success_log $error_log
if($internet_available -eq $true) {
$success_log = Join-Path $Script:LogPath ($timestamp + ".backup.log.txt")
$error_log = Join-Path $Script:LogPath ($timestamp + ".backup.err.txt")
$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
"[[Backup]] Succeeded after $total_attempts attempt(s)" | Tee-Object -Append $success_log | Write-Host
# test to see if maintenance is needed if the backup was successful
$maintenance_needed = Test-Maintenance $success_log $error_log
}
else {
"[[Backup]] Ran with errors on attempt $total_attempts" | Tee-Object -Append $success_log | Tee-Object -Append $error_log | Write-Host
$error_count++
}
}
Write-Warning "Errors found! Error Log: $error_log"
$error_count++
Write-Output "Something went wrong. Sleeping for 15 min and then retrying..." | Tee-Object -Append $success_log
if($internet_available -eq $true) {
Send-Email $success_log $error_log
else {
"[[Backup]] Failed - cannot access repository." | Tee-Object -Append $success_log | Tee-Object -Append $error_log | Write-Host
$error_count++
}
Start-Sleep (15*60)
$attempt_count--
}
# update logs prior to sending email
if($backup_success -eq $false) {
if($attempt_count -gt 0) {
"[[Backup]] Sleeping for 15 min and then retrying..." | Tee-Object -Append $success_log | Write-Host
}
else {
"[[Backup]] Retry limit has been reached. No more attempts to backup will be made." | Tee-Object -Append $success_log | Write-Host
}
}
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 $Script:LogPath ($timestamp + ".maintenance.log.txt")
$error_log = Join-Path $Script: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) {
"[[Maintenance]] Succeeded after $total_attempts attempt(s)" | Tee-Object -Append $success_log | Write-Host
}
else {
"[[Maintenance]] Ran with errors on attempt $total_attempts" | Tee-Object -Append $success_log | Tee-Object -Append $error_log | Write-Host
$error_count++
}
}
else {
"[[Maintenance]] Failed - cannot access repository." | Tee-Object -Append $success_log | Tee-Object -Append $error_log | Write-Host
$error_count++
}
$attempt_count--
# update logs prior to sending email
if($maintenance_success -eq $false) {
if($attempt_count -gt 0) {
"[[Maintenance]] Sleeping for 15 min and then retrying..." | Tee-Object -Append $success_log | Write-Host
}
else {
"[[Maintenance]] Retry limit has been reached. No more attempts to run maintenance will be made." | Tee-Object -Append $success_log | Write-Host
}
}
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
}
}
# custom end actions
if((-not $backup_success) -or ($maintenance_needed -and -not $maintenance_success)) {
# call the custom error action if backup failed and/or maintenance was needed and failed
if($null -ne $CustomActionEndError) {
Invoke-Expression $CustomActionEndError
}
}
else {
# call custom success action if backup & maintenance were successful
if($null -ne $CustomActionEndSuccess) {
Invoke-Expression $CustomActionEndSuccess
}
}
# Save state to file
Set-BackupState
# cleanup older log files
Get-ChildItem $LogPath | Where-Object {$_.CreationTime -lt $(Get-Date).AddDays(-$LogRetentionDays)} | Remove-Item
Get-ChildItem $Script:LogPath | Where-Object {$_.CreationTime -lt $(Get-Date).AddDays(-$LogRetentionDays)} | Remove-Item
exit $error_count
}
Invoke-Main
Invoke-Main

View File

@@ -1,24 +0,0 @@
# backup configuration
$ExeName = "restic.exe"
$InstallPath = "C:\restic"
$ResticExe = Join-Path $InstallPath $ExeName
$StateFile = Join-Path $InstallPath "state.xml"
$WindowsExcludeFile = Join-Path $InstallPath "windows.exclude"
$LocalExcludeFile = Join-Path $InstallPath "local.exclude"
$LogPath = Join-Path $InstallPath "logs"
$LogRetentionDays = 30
$SnapshotRetentionPolicy = @("--keep-daily", "30", "--keep-weekly", "52", "--keep-monthly", "24", "--keep-yearly", "10")
$SnapshotMaintenanceInterval = 7
$SnapshotMaintenanceDays = 30
$SnapshotDeepMaintenanceDays = 90;
$InternetTestAttempts = 10
$GlobalRetryAttempts = 4
# Paths to backup
$BackupSources = @{}
$BackupSources["C:\"] = @(
# 'Users'
)
#$BackupSources["D:\"] = @(
# 'Software'
#)

66
config_sample.ps1 Normal file
View File

@@ -0,0 +1,66 @@
# Sample configuration file
# Update this file to control how the restic backup, forget, and purge operations are run
# Rename to `config.ps1`
# general configuration
$InstallPath = "C:\restic"
$ExeName = "restic.exe"
$GlobalParameters = @()
$LogRetentionDays = 30
$BackupOnMeteredNetwork = $true
$InternetTestAttempts = 10
$GlobalRetryAttempts = 4
# email configuration
$SendEmailOnSuccess = $false
$SendEmailOnError = $true
# backup configuration
$WindowsExcludeFile = Join-Path $InstallPath "windows.exclude"
$LocalExcludeFile = Join-Path $InstallPath "local.exclude"
$IgnoreMissingBackupSources = $false
$AdditionalBackupParameters = @("--exclude-if-present", ".nobackup", "--no-scan")
# Paths to backup
$BackupSources = @{}
$BackupSources["C:\"] = @(
# "Users\Example\Desktop\Source1",
# "Users\Example\Desktop\Source2"
)
# $BackupSources["D:\"] = @(
# "Example\Source3",
# "Example\Source4"
# )
#$BackupSources["DRIVE_LABEL_NAME_OR_SERIAL_NUMBER"] = @(
# "Example\FolderName"
#)
# maintenance configuration
$SnapshotMaintenanceEnabled = $true
$SnapshotRetentionPolicy = @("--host", $env:COMPUTERNAME, "--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
# restic.exe self update configuration
$SelfUpdateEnabled = $true
# (optional) custom actions
# Define commands to pass to Invoke-Expression at script start and script end
# note: Errors will only be reported if the script does not eventually succeed. Errors
# from unsuccessful attempts to backup or maintain the repository will not result
# in the custom error action being called unless all attempts to backup or maintain failed.
$CustomActionStart = $null
$CustomActionEndError = $null
$CustomActionEndSuccess = $null
# Example: Calling a healthcheck remote service
# $healthCheckURL = "https://healthcheckservice.com/etc/etc"
# $CustomActionStart = "Invoke-RestMethod $healthCheckURL/start"
# $CustomActionEndError = "Invoke-RestMethod $healthCheckURL/fail"
# $CustomActionEndSuccess = "Invoke-RestMethod $healthCheckURL"
# Example: Invoking a script
# $successScript = Join-Path $InstallPath "mySuccessScript.ps1"
# $CustomActionEndSuccess = "& $successScript"

View File

@@ -1,21 +1,70 @@
. .\config.ps1
. .\secrets.ps1
#
# Restic Windows Backup - Installation Script
#
# =========== start configuration =========== #
# load restic configuration parmeters (destination, passwords, etc.)
$SecretsScript = Join-Path $PSScriptRoot "secrets.ps1"
# load backup configuration variables
$ConfigScript = Join-Path $PSScriptRoot "config.ps1"
# initialize secrets
. $SecretsScript
# initialize config
. $ConfigScript
# apply global configuration
$ResticExe = Join-Path $InstallPath $ExeName
$LogPath = Join-Path $InstallPath "logs"
# make LASTEXITCODE global to enable error checking for Invoke-Expression commands
$global:LASTEXITCODE=0
# =========== end configuration =========== #
# download restic
if(-not (Test-Path $ResticExe)) {
$url = "https://github.com/restic/restic/releases/download/v0.9.6/restic_0.9.6_windows_amd64.zip"
$output = Join-Path $InstallPath "restic.zip"
Invoke-WebRequest -Uri $url -OutFile $output
Expand-Archive -LiteralPath $output $InstallPath
Remove-Item $output
Get-ChildItem *.exe | Rename-Item -NewName $ExeName
$url = $null
if([Environment]::Is64BitOperatingSystem){
$url = "https://github.com/restic/restic/releases/download/v0.17.3/restic_0.17.3_windows_amd64.zip"
}
else {
$url = "https://github.com/restic/restic/releases/download/v0.17.3/restic_0.17.3_windows_386.zip"
}
try {
$output = Join-Path $InstallPath "restic.zip"
Invoke-WebRequest -Uri $url -OutFile $output
Expand-Archive -LiteralPath $output $InstallPath
Remove-Item $output
Get-ChildItem *.exe | Rename-Item -NewName $ExeName
}
catch {
Write-Error "[[Install]] restic.exe download failed. Check errors and resolve: $_"
exit 1
}
}
# Apply global paramters to $ResticExe, after the $ResticExe has been downloaded/confirmed to exist
if(-not [String]::IsNullOrEmpty($GlobalParameters)) {
$ResticExe = "$ResticExe $GlobalParameters"
}
# Invoke restic self-update to check for a newer version
# This is enabled by default unless configuration disables self-update
if ([String]::IsNullOrEmpty($SelfUpdateEnabled) -or ($SelfUpdateEnabled -eq $true)) {
Invoke-Expression "$ResticExe self-update"
if($LASTEXITCODE) {
Write-Warning "[[Update]] Restic self-update failed. Check errors and resolve."
}
}
# Create log directory if it doesn't exit
if(-not (Test-Path $LogPath)) {
New-Item -ItemType Directory -Force -Path $LogPath | Out-Null
Write-Output "[[Init]] Repository successfully initialized."
Write-Output "[[Init]] Created log directory: $LogPath"
}
# Create the local exclude file
@@ -24,12 +73,12 @@ if(-not (Test-Path $LocalExcludeFile)) {
}
# Initialize the restic repository
& $ResticExe --verbose init
if($?) {
Write-Output "[[Init]] Repository successfully initialized."
Invoke-Expression "$ResticExe --verbose init"
if($LASTEXITCODE) {
Write-Warning "[[Init]] Repository initialization failed. Check errors and resolve."
}
else {
Write-Warning "[[Init]] Repository initialization failed. Check errors and resolve."
Write-Output "[[Init]] Repository successfully initialized."
}
# Scheduled Windows Task Scheduler to run the backup
@@ -37,19 +86,23 @@ $backup_task_name = "Restic Backup"
$backup_task = Get-ScheduledTask $backup_task_name -ErrorAction SilentlyContinue
if($null -eq $backup_task) {
try {
$task_action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-NonInteractive -NoLogo -NoProfile -Command ".\backup.ps1; exit $LASTEXITCODE"' -WorkingDirectory $InstallPath
$task_user = New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\SYSTEM" -RunLevel Highest
$task_action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-ExecutionPolicy Bypass -NonInteractive -NoLogo -NoProfile -Command ".\backup.ps1; exit $LASTEXITCODE"' -WorkingDirectory $InstallPath
$task_user = New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$task_settings = New-ScheduledTaskSettingsSet -RestartCount 4 -RestartInterval (New-TimeSpan -Minutes 15) -ExecutionTimeLimit (New-TimeSpan -Days 3) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -DontStopOnIdleEnd -MultipleInstances IgnoreNew -IdleDuration 0 -IdleWaitTimeout 0 -StartWhenAvailable -RestartOnIdle
$task_trigger = New-ScheduledTaskTrigger -Daily -At 4:00am
Register-ScheduledTask $backup_task_name -Action $task_action -Principal $task_user -Settings $task_settings -Trigger $task_trigger | Out-Null
Write-Output "[[Scheduler]] Backup task scheduled."
}
catch {
Write-Warning "[[Scheduler]] Scheduling failed."
Write-Error "[[Scheduler]] Setting up backup task schedule failed: $_"
}
}
else {
Write-Warning "[[Scheduler]] Backup task not scheduled: there is already a task with the name '$backup_task_name'."
}
# Install NuGet and Send-MailKitMessage module (by force)
if ($PSVersionTable.PSVersion.Major -eq 5) {
Install-PackageProvider -Name NuGet -Force
}
Install-Module Send-MailKitMessage -Repository PSGallery -Scope AllUsers -Force

View File

@@ -9,9 +9,9 @@ $Env:RESTIC_REPOSITORY='<REPO URL>'
$Env:RESTIC_PASSWORD='<BACKUP PASSWORD>'
# email configuration
$PSEmailServer='<SMTP SERVER>'
$ResticEmailConfig=@{UseSsl=$true; Port="587"}
$ResticEmailServer='<SMTP SERVER>'
$ResticEmailPort='<SMTP PORT NUMBER, i.e. 25 or 587>'
$ResticEmailTo='<DESTINATION EMAIL ADDRESS>'
$ResticEmailFrom='<FROM EMAIL ADDRESS>'
$ResticEmailUsername='<EMAIL LOGIN USERNAME>'
$ResticEmailPassword='<EMAIL PASSWORD>'
$ResticEmailUsername='<EMAIL LOGIN USERNAME OR EMPTY FOR NO USERNAME>'
$ResticEmailPassword='<EMAIL PASSWORD OR EMPTY FOR NO PASSWORD>'

337
update.ps1 Normal file
View File

@@ -0,0 +1,337 @@
<#
.SYNOPSIS
Updates the local installed restic backup scripts from GitHub,
either using the latest tagged release or by targeting a specific branch.
.DESCRIPTION
This script supports two modes:
1. **Release mode (default):**
- Fetches the latest release info via GitHubs API.
- Compares the release tag (after normalization) against a locally stored version (in state.xml).
- If the GitHub release is newer, downloads the release zip, extracts it, copies the files
over the local installation.
2. **Branch mode:**
- Targets a specific branch (default "main") by retrieving branch information from GitHub.
- Compares the latest commit SHA on that branch against a locally stored SHA (in state.xml).
- If the remote commit SHA differs, downloads the branch zip archive, extracts it,
copies the files over the local installation.
.NOTES
Example 1 - update scripts to the latest tagged release
.\update.ps1
Example 2 - update scripts from a branch
.\update.ps1 -Mode branch -BranchName 'release_1.8'
Example 3 - download a new copy of the update scripts and run it
1. Change your directory to your installation directory (e.g. `cd c:\restic`)
2. Invoke-WebRequest "https://raw.githubusercontent.com/kmwoley/restic-windows-backup/main/update.ps1" -OutFile update.ps1
3. .\update.ps1
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[ValidateSet("release", "branch")]
[string]$Mode = "release",
[Parameter(Mandatory = $false)]
[string]$BranchName = "main",
[Parameter(Mandatory = $false)]
[string]$InstallPath = $null
)
# ====================================
# Configuration and Setup
# ====================================
# GitHub repository details
$repoOwner = "kmwoley"
$repoName = "restic-windows-backup"
# User-Agent header (GitHub requires this)
$headers = @{ "User-Agent" = "PowerShell" }
# default the installation directory to the location of the running script
if([string]::IsNullOrEmpty($InstallPath)) {
# default to the script's location, if running as a script
$InstallPath = $PSScriptRoot
if([string]::IsNullOrEmpty($InstallPath)) {
# default to the current working directory, if not running as a script
$InstallPath = Get-Location
}
}
# ====================================
# Functions for state management
# ====================================
function Get-State {
if(Test-Path $Script:StateFile) {
Import-Clixml $Script:StateFile | ForEach-Object{ Set-Variable -Scope Script $_.Name $_.Value }
}
}
function Set-State {
Get-Variable ResticState* | Export-Clixml $Script:StateFile
}
# ===========================================
# Functions for file management and download
# ===========================================
function Get-ModifiedFiles {
param(
[Parameter(Mandatory = $true)]
[string]$Source,
[Parameter(Mandatory = $true)]
[string]$Destination,
[Parameter(Mandatory = $true)]
[string]$DateTime
)
$modifiedFiles = New-Object System.Collections.Generic.List[System.Object]
if(-not (Test-Path $Source)) {
Write-Error "Source does not exist ($Source)"
exit 1
}
if(-not (Test-Path $Destination)) {
Write-Error "Destination does not exist ($Destination)"
exit 1
}
$sourceFiles = Get-ChildItem $Source
ForEach ($sourceFile in $sourceFiles) {
# find if there's a corrosponding file in the destination
$destFileName = Join-Path $Destination $sourceFile.Name
if(Test-Path $destFileName) {
$destFile = Get-ChildItem $destFileName
if($destFile.LastWriteTime -gt $DateTime) {
# destination file has been modified after $DateTime
$modifiedFiles.Add($destFile.FullName)
}
}
}
return $modifiedFiles
}
function Update-InstalledScripts {
param(
[Parameter(Mandatory=$true)][string]$ZipUrl,
[Parameter(Mandatory=$true)][string]$DestinationFolder
)
$timestamp = Get-Date -Format FileDateTime
$tempExtractDir = Join-Path $env:TEMP ("restic-windows-backup." + $timestamp)
$tempZipPath = Join-Path $env:TEMP ("restic-windows-backup." + $timestamp + ".zip")
# test temp location, fail if in use
if (Test-Path $tempExtractDir) {
Write-Error "Temporary directory already exists: $tempExtractDir"
exit 1
}
if (Test-Path $tempZipPath) {
Write-Error "Temporary directory already exists: $tempZipPath"
exit 1
}
# Create a temporary folder for extraction
New-Item -ItemType Directory -Path $tempExtractDir | Out-Null
Write-Host "Downloading from: $ZipUrl"
try {
Invoke-WebRequest -Uri $ZipUrl -OutFile $tempZipPath -Headers $headers
} catch {
Write-Error "Failed to download the file: $_"
exit 1
}
try {
Expand-Archive -LiteralPath $tempZipPath $tempExtractDir
} catch {
Write-Error "Error extracting zip file: $_"
exit 1
}
# Determine the actual folder containing the repository files.
$extractedContent = Get-ChildItem -Path $tempExtractDir | Where-Object { $_.PSIsContainer }
if ($extractedContent.Count -eq 1) {
$extractedFolder = $extractedContent[0].FullName
} else {
$extractedFolder = $tempExtractDir
}
# Check to make sure not to overwrite modified files
$installedDate = $Script:ResticStateInstalledDate
if([string]::IsNullOrEmpty($installedDate)) {
# unkown install date; setting the date
$installedDate = [datetime]::MinValue
}
$modifiedFiles = Get-ModifiedFiles -Source $extractedFolder -Destination $DestinationFolder -DateTime $installedDate
if($modifiedFiles) {
if([string]::IsNullOrEmpty($Script:ResticStateInstalledDate)) {
Write-Host "WARNING: The following files already exist in the target directory"
}
else {
Write-Host "WARNING: The following files have been modified since they were installed on $installedDate"
}
ForEach ($fileName in $modifiedFiles) {
Write-Host " - " $fileName
}
# TODO: add a "-Force" parameter to skip this check/question
Write-Host "Continuing will overwrite these files."
Write-host "Do you want to continue?"
$userInput = Read-Host "[Y] Yes [N] No (default is ""Y"")"
if ($userInput -ieq 'n') {
Write-Host "Operation cancelled."
exit 0
}
}
Write-Host "Updating files in installation directory ($DestinationFolder)..."
try {
# Recursively copy all content from the extracted folder to the local directory.
Copy-Item -Path (Join-Path $extractedFolder "*") -Destination $DestinationFolder -Recurse -Force
} catch {
Write-Error "Error copying files: $_"
exit 1
}
# Clean up temporary files
Remove-Item $tempZipPath -Force
Remove-Item $tempExtractDir -Recurse -Force
}
# ====================================
# Main
# ====================================
# load restic state
$Script:ResticStateInstalledVersion = $null
$Script:ResticStateInstalledBranchSHA = $null
$Script:ResticStateInstalledDate = $null
$Script:StateFile = Join-Path $InstallPath "state.xml"
Get-State
# ====================================
# Release mode
# ====================================
if ($Mode -eq "release") {
# Read the version of the scripts installed
$localVersion = $Script:ResticStateInstalledVersion
if ([string]::IsNullOrEmpty($localVersion)) {
# No version information stored locally
$localVersion = "0.0.0"
}
# Get the Latest Release Info from GitHub
$releaseApiUrl = "https://api.github.com/repos/$repoOwner/$repoName/releases/latest"
try {
Write-Host "Checking GitHub for latest release of '$repoOwner/$repoName'..."
$release = Invoke-RestMethod -Uri $releaseApiUrl -Headers $headers
} catch {
Write-Error "Error fetching release information from GitHub: $_"
exit 1
}
$latestTagRaw = $release.tag_name
$latestTag = $latestTagRaw.Trim()
# Normalize versions (remove leading "v" if present)
function Get-NormalizedVersion($versionString) {
if ($versionString.StartsWith("v", [System.StringComparison]::InvariantCultureIgnoreCase)) {
return $versionString.Substring(1)
}
return $versionString
}
$normalizedLocalVersion = Get-NormalizedVersion $localVersion
$normalizedLatestVersion = Get-NormalizedVersion $latestTag
try {
$localVersionObj = [Version]$normalizedLocalVersion
$latestVersionObj = [Version]$normalizedLatestVersion
} catch {
Write-Error "Error parsing version strings. Local: $normalizedLocalVersion, Latest: $normalizedLatestVersion. $_"
exit 1
}
if ($latestVersionObj -le $localVersionObj) {
Write-Host "Installed version ($localVersionObj) is up-to-date. No update needed."
exit 0
} else {
Write-Host "Newer release available: $latestVersionObj (installed: $localVersionObj). Proceeding with update..."
}
# get the zip URL from the release info
$zipUrl = $release.zipball_url
# Download and update the installed scripts
Update-InstalledScripts -ZipUrl $zipUrl -DestinationFolder $InstallPath
# Store the installed version number and time installed
$Script:ResticStateInstalledVersion = $normalizedLatestVersion
$Script:ResticStateInstalledDate = Get-Date
$Script:ResticStateInstalledBranchSHA = $null
Set-State
Write-Host "Update successful. Installed version is now $normalizedLatestVersion."
}
# ====================================
# Branch mode
# ====================================
elseif ($Mode -eq "branch") {
# Read the SHA of the branch source installed
$localCommitSHA = $Script:ResticStateInstalledBranchSHA
if ([string]::IsNullOrEmpty($localCommitSHA)) {
# Write-Host "No branch information stored locally."
$localCommitSHA = "unknown"
}
# Retrieve branch information from GitHub
$branchApiUrl = "https://api.github.com/repos/$repoOwner/$repoName/branches/$BranchName"
try {
Write-Host "Checking GitHub for latest commit of '$repoOwner/$repoName' on branch '$BranchName'..."
$branchInfo = Invoke-RestMethod -Uri $branchApiUrl -Headers $headers
} catch {
Write-Error "Error fetching branch information from GitHub: $_"
exit 1
}
$latestCommitSHA = $branchInfo.commit.sha
if ($localCommitSHA -eq $latestCommitSHA) {
Write-Host "Installed commit ($latestCommitSHA) is up-to-date. No update needed."
exit 0
} else {
Write-Host "Latest commit: $latestCommitSHA (installed: $localCommitSHA). Proceeding with update..."
}
# Construct the zip URL for the branch.
# GitHub provides branch archives at:
# https://github.com/{owner}/{repo}/archive/refs/heads/{branch}.zip
$zipUrl = "https://github.com/$repoOwner/$repoName/archive/refs/heads/$BranchName.zip"
# Download and update the installed scripts
Update-InstalledScripts -ZipUrl $zipUrl -DestinationFolder $InstallPath
# Store the installed branch commit SHA and time installed
$Script:ResticStateInstalledVersion = $null
$Script:ResticStateInstalledDate = Get-Date
$Script:ResticStateInstalledBranchSHA = $latestCommitSHA
Set-State
Write-Host "Update successful. Local branch is now at commit $latestCommitSHA."
}
else {
Write-Error "Unsupported mode."
exit 1
}

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,8 @@ UsrClass.dat
Dropbox
AppData\Local\Google\Drive
Google Drive\.tmp.drivedownload
resticVSS\OneDriveTemp
C:\OneDriveTemp
Users\**\Nextcloud
# browsers
Google\Chrome
@@ -45,13 +48,15 @@ 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
AppData\Local\Spotify
AppData\Local\Programs\signal-desktop
AppData\Roaming\Code
AppData\Roaming\Slack
AppData\Roaming\Spotify
AppData\Roaming\Zoom
# misc. temporary files
Temporary Internet Files