├── .gitattributes ├── README.md └── DailyBackup.ps1 /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Macrium Backup 2 | Macrium Backup - Powershell 3 | 4 | ### .SYNOPSIS 5 | Macrium Reflect Daily Backup 6 | 7 | ### .DESCRIPTION 8 | Script to run and send notifications for Macrium Reflect 9 | 10 | ### .FUNCTIONALITY 11 | * Frees up space on target drive if necessary 12 | * Runs Macrium Reflect using specified XML config 13 | * Copies Macrium html log file to webserver 14 | * Creates short link to log file 15 | * Emails report with link to log file 16 | 17 | ### .PARAMETER 18 | * -s = ? 19 | * -full = full backup 20 | * -inc = incremental backup 21 | * -diff = differential backup 22 | * [empty] = clone 23 | 24 | ### .NOTES 25 | Run daily from task scheduler with administrator privileges 26 | 27 | ### .EXAMPLE 28 | PS C:\Windows\system32> C:\scripts\Reflect\DailyBackup.ps1 -diff 29 | -------------------------------------------------------------------------------- /DailyBackup.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | .SYNOPSIS 4 | Macrium Reflect Backup 5 | 6 | .DESCRIPTION 7 | Script to run and send notifications for Macrium Reflect 8 | 9 | .FUNCTIONALITY 10 | * Frees up space on target drive if necessary 11 | * Runs Macrium Reflect using specified XML config 12 | * Copies Macrium html log file to webserver 13 | * Creates short link to log file 14 | * Emails report with link to log file 15 | 16 | .PARAMETER 17 | -s = ? 18 | -full = full backup 19 | -inc = incremental backup 20 | -diff = differential backup 21 | [empty] = clone 22 | 23 | .NOTES 24 | Run daily from task scheduler with administrator privileges 25 | 26 | .EXAMPLE 27 | PS C:\Windows\system32> C:\scripts\Reflect\DailyBackup.ps1 -diff 28 | 29 | #> 30 | 31 | 32 | <### SCRIPT PARAMETERS ###> 33 | Param([switch]$s, [switch]$full, [switch]$inc, [switch]$diff) 34 | 35 | 36 | <### USER VARIABLES ###> 37 | 38 | <# Reflect Variables #> 39 | $strReflectPath = "C:\Program Files\Macrium\Reflect\reflect.exe" 40 | $strXmlFilePath = "C:\Users\User\Documents\Reflect\DailyBackup.xml" 41 | 42 | <# AutoDelete Variables #> 43 | $DoDelete = $True # For testing - set to false to run and report without deleting any old image files 44 | $ImageDir = "D:\Images" # Target folder for backup images 45 | $BackupsToKeep = 3 # How many differential files should be kept 46 | $Safety = 2 # Multiplier of largest file size - used to compare to drive free space to ensure image target drive has enough free space to fit new backup image 47 | 48 | <# Email Variables #> 49 | $EmailFrom = "notify@mydomain.tld" 50 | $EmailTo = "admin@mydomain.tld" 51 | $Subject = "Macrium Nightly Backup" 52 | $SMTPServer = "mydomain.tld" 53 | $SMTPAuthUser = "notify@mydomain.tld" 54 | $SMTPAuthPass = "supersecretpassword" 55 | $SMTPPort = 587 56 | $SSL = $True # If true, will use tls connection to send email 57 | $UseHTML = $True # If true, will format and send email body as html (with color!) 58 | 59 | <# Log Variables #> 60 | $MacriumLogDir = "C:\ProgramData\Macrium\Reflect" # Default location 61 | $WebDir = "X:\xampp\htdocs\mydomain.tld\maclog" # File location of web dir to place log files for online viewing 62 | $WebBaseURL = "https://maclog.mydomain.tld/" # Please leave trailing slash "/" 63 | $YoURLsToken = 'secrettoken' # YoURLs API token 64 | $YoURLsURI = 'https://url.mydomain.tld' # YoURLs Base URL 65 | 66 | 67 | <### FUNCTIONS ###> 68 | 69 | Function Email ($EmailOutput) { 70 | If ($UseHTML){ 71 | If ($EmailOutput -match "\[OK\]") {$EmailOutput = $EmailOutput -Replace "\[OK\]","[OK]"} 72 | If ($EmailOutput -match "\[INFO\]") {$EmailOutput = $EmailOutput -Replace "\[INFO\]","[INFO]"} 73 | If ($EmailOutput -match "\[ERROR\]") {$EmailOutput = $EmailOutput -Replace "\[ERROR\]","[ERROR]"} 74 | If ($EmailOutput -match "^\s$") {$EmailOutput = $EmailOutput -Replace "\s"," "} 75 | Write-Output "$EmailOutput" | Out-File $EmailBody -Encoding ASCII -Append 76 | } Else { 77 | Write-Output $EmailOutput | Out-File $EmailBody -Encoding ASCII -Append 78 | } 79 | } 80 | 81 | Function EmailResults { 82 | If ($UseHTML) {Write-Output "" | Out-File $EmailBody -Encoding ASCII -Append} 83 | Try { 84 | $Body = (Get-Content -Path $EmailBody | Out-String ) 85 | $Message = New-Object System.Net.Mail.Mailmessage $EmailFrom, $EmailTo, $Subject, $Body 86 | $Message.IsBodyHTML = $UseHTML 87 | $SMTP = New-Object System.Net.Mail.SMTPClient $SMTPServer,$SMTPPort 88 | $SMTP.EnableSsl = $SSL 89 | $SMTP.Credentials = New-Object System.Net.NetworkCredential($SMTPAuthUser, $SMTPAuthPass); 90 | $SMTP.Send($Message) 91 | } 92 | Catch { 93 | Write-Output "$(Get-Date -f G) : [ERROR] EmailResults Function : $($Error[0])" | Out-File "$PSScriptRoot\MacriumErrorLog-$DateString.log" -Encoding ASCII -Append 94 | } 95 | } 96 | 97 | Function ElapsedTime ($EndTime) { 98 | $TimeSpan = New-Timespan $EndTime 99 | If (([int]($TimeSpan).Hours) -eq 0) {$Hours = ""} ElseIf (([int]($TimeSpan).Hours) -eq 1) {$Hours = "1 hour "} Else {$Hours = "$([int]($TimeSpan).Hours) hours "} 100 | If (([int]($TimeSpan).Minutes) -eq 0) {$Minutes = ""} ElseIf (([int]($TimeSpan).Minutes) -eq 1) {$Minutes = "1 minute "} Else {$Minutes = "$([int]($TimeSpan).Minutes) minutes "} 101 | If (([int]($TimeSpan).Seconds) -eq 1) {$Seconds = "1 second"} Else {$Seconds = "$([int]($TimeSpan).Seconds) seconds"} 102 | 103 | If (($TimeSpan).TotalSeconds -lt 1) { 104 | $Return = "less than 1 second" 105 | } Else { 106 | $Return = "$Hours$Minutes$Seconds" 107 | } 108 | Return $Return 109 | } 110 | 111 | Function CopyLogFile { 112 | $MacriumLog = (Get-ChildItem $MacriumLogDir -Attributes !Directory *.html | Sort-Object -Descending -Property LastWriteTime | Select -First 1) 113 | Copy-Item $MacriumLog.FullName -Destination $WebDir 114 | Add-Type -AssemblyName System.Web 115 | $LongURL = [System.Web.HTTPUtility]::UrlEncode($WebBaseURL + $MacriumLog.Name) 116 | $YoURLsAPI = $YoURLsURI + "/yourls-api.php?title=MacriumLog&signature=" + $YoURLsToken + "&action=shorturl&format=json&url=" + $LongURL 117 | $ShortURL = (Invoke-RestMethod $YoURLsAPI).shorturl 118 | Return $ShortURL 119 | } 120 | 121 | Function Backup(){ 122 | $strType = GetBackupTypeParameter 123 | $strArgs = "-e -w $strType `"$strXmlFilePath`"" 124 | $RunBackup = Start-Process -FilePath $strReflectPath -ArgumentList $strArgs -PassThru -Wait 125 | $iResult = $RunBackup.ExitCode 126 | switch ($iResult){ 127 | 2 { Email "[ERROR] XML invalid - See log for error"; break; } 128 | 1 { Email "[ERROR] Backup failed - See log for error"; break; } 129 | 0 { Email "[OK] Backup succeeded"; break; } 130 | } 131 | return $iResult 132 | } 133 | 134 | Function GetBackupTypeParameter(){ 135 | if ($full -eq $true) { return '-full'; } 136 | if ($inc -eq $true) { return '-inc'; } 137 | if ($diff -eq $true) { return '-diff'; } 138 | return ''; # Clone 139 | } 140 | 141 | Function AutoDelete { 142 | $LargestBackup = (Get-ChildItem $ImageDir | Sort-Object -Descending -Property Length | Select -First 1).Length 143 | $Drive = "DeviceID='" + $ImageDir.Substring(0,1) + ":'" 144 | $Disk = Get-WmiObject Win32_LogicalDisk -Filter $Drive | Foreach-Object { 145 | $Size = $_.Size 146 | $Free = $_.FreeSpace 147 | } 148 | 149 | If ($Free -gt ($LargestBackup * $Safety)) { 150 | Email "[OK] Adequate disk space" 151 | $Return = $False 152 | } Else { 153 | Email "[INFO] Backup image removal:" 154 | Get-ChildItem $ImageDir | Group-Object -Property { $_.Name.Substring(0, 16) } | Sort-Object -Descending -Property LastWriteTime | ForEach { 155 | $GroupName = $_.Name 156 | $GroupName | ForEach { 157 | $GroupFiles = Get-ChildItem $ImageDir | Where {$_.Name -match ($GroupName)} | Sort-Object -Descending -Property LastWriteTime 158 | $GroupCount = $GroupFiles.Count 159 | $N = 0 160 | ForEach ($File in $GroupFiles) { 161 | If (($N -gt ($BackupsToKeep - 1)) -and ($N -lt ($GroupCount - 1))) { 162 | If ($DoDelete) {Remove-Item $File.FullName} 163 | } 164 | $N++ 165 | } 166 | } 167 | } 168 | $Return = $True 169 | } 170 | Start-Sleep -Seconds 2 171 | Return $Return 172 | } 173 | 174 | Function AutoDeleteSet { 175 | $LargestBackup = (Get-ChildItem $ImageDir | Sort-Object -Descending -Property Length | Select -First 1).Length 176 | $Drive = "DeviceID='" + $ImageDir.Substring(0,1) + ":'" 177 | $Disk = Get-WmiObject Win32_LogicalDisk -Filter $Drive | Foreach-Object { 178 | $Size = $_.Size 179 | $Free = $_.FreeSpace 180 | } 181 | 182 | If ($Free -gt ($LargestBackup * $Safety)) { 183 | Email "[OK] Adequate disk space" 184 | $Return = $False 185 | } Else { 186 | Email "[INFO] Backup image set removal" 187 | Get-ChildItem $ImageDir | Group-Object -Property { $_.Name.Substring(0, 16) } | Sort-Object -Descending -Property LastWriteTime | Select -Last 1 | ForEach { 188 | $GroupName = $_.Name 189 | $GroupName | ForEach { 190 | $GroupFiles = Get-ChildItem $ImageDir | Where {$_.Name -match ($GroupName)} | Sort-Object -Descending -Property LastWriteTime | ForEach { 191 | If ($DoDelete) { 192 | Email "Deleted Image: $($_.Name)" 193 | Remove-Item $_.FullName 194 | } Else { 195 | Email "TEST ONLY: Deleted Image: $($_.Name)" 196 | } 197 | } 198 | } 199 | } 200 | $Return = $True 201 | } 202 | Start-Sleep -Seconds 2 203 | Return $Return 204 | } 205 | 206 | 207 | <### START SCRIPT ###> 208 | 209 | $StartScript = Get-Date 210 | $DateString = (Get-Date).ToString("yyyy-MM-dd") 211 | 212 | <# Delete old email and create new #> 213 | $EmailBody = "$PSScriptRoot\EmailBody.log" 214 | If (Test-Path $EmailBody) {Remove-Item -Force -Path $EmailBody} 215 | New-Item $EmailBody 216 | If ($UseHTML) { 217 | Write-Output " 218 | 219 | 220 | 221 | " | Out-File $EmailBody -Encoding ASCII -Append 222 | } 223 | If ($UseHTML) { 224 | Email "
:::   Macrium Backup Routine   :::
" 225 | Email "
$(Get-Date -f D)
" 226 | Email " " 227 | } Else { 228 | Email "::: Macrium Backup Routine :::" 229 | Email " $(Get-Date -f D)" 230 | Email " " 231 | Email " " 232 | } 233 | 234 | Email "Macrium Reflect Backup Definition File:" 235 | Email "$strXmlFilePath" 236 | Email " " 237 | 238 | 239 | <### AUTODELETE OLD IMAGE FILES ###> 240 | 241 | If (-not($inc)) { 242 | $DeleteMore = AutoDelete 243 | If ($DeleteMore) { 244 | $DeleteEvenMore = AutoDeleteSet 245 | If ($DeleteEvenMore) { 246 | $DeleteLastTry = AutoDeleteSet 247 | If ($DeleteLastTry) { 248 | Email "[ERROR] Could not remove enough files to free up space for new backup image. Quitting Script." 249 | EmailResults 250 | Exit 251 | } 252 | } 253 | } 254 | } Else { 255 | $DeleteEvenMore = AutoDeleteSet 256 | If ($DeleteEvenMore) { 257 | $DeleteLastTry = AutoDeleteSet 258 | If ($DeleteLastTry) { 259 | Email "[ERROR] Could not remove enough files to free up space for new backup image. Quitting Script." 260 | EmailResults 261 | Exit 262 | } 263 | } 264 | } 265 | 266 | 267 | <### RUN BACKUP ###> 268 | 269 | $iExitCode = Backup 270 | 271 | $LastBackup = Get-ChildItem $ImageDir | Sort LastWriteTime -Descending | Select -First 1 272 | $LastBackupName = $LastBackup.Name 273 | $LastBackupSize = $("{0:N2}" -f (($LastBackup.Length)/1GB)) 274 | 275 | Email " " 276 | Email "Created image $LastBackupName : $LastBackupSize GB" 277 | Email "Script finished with exit code $iExitCode." 278 | 279 | <# Finish up and send email #> 280 | Email "Macrium Backup routine completed in $(ElapsedTime $StartScript)" 281 | Email " " 282 | $LogURILong = CopyLogFile 283 | $LogURIShort = $LogURILong -Replace "https://", "" 284 | If ($UseHTML) { Email "Debug Log: $LogURIShort" } Else { Email "Debug Log: $LogURILong" } 285 | EmailResults 286 | 287 | <# Exit with exit code #> 288 | Exit $iExitCode --------------------------------------------------------------------------------