Last active
February 4, 2019 22:09
-
-
Save kiwi-cam/24cb97a68b6b60c0c795dd8df4948812 to your computer and use it in GitHub Desktop.
This script runs Robycopy to duplicate a source and destination drive for backups. It then parses the results and emails admins.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[CmdletBinding()] | |
param() | |
#Main Variables adjusted | |
$Dest = "F:\" | |
$LogFile = "C:\Admin\RobocopyBackup.log" | |
$ReportTo = @("[email protected]","[email protected]") | |
$LogSummary = "" | |
$Region = "Auckland" | |
#Collection of Sources and Swtiches for RoboCopy | |
$Sources = New-Object System.Data.DataTable | |
$Sources.Columns.Add("Path","string") | Out-Null | |
$Sources.Columns.Add("Switches","string") | Out-Null | |
$Sources.Rows.Add("E:\Company\","/R:1 /W:1 /MT:128 /LOG:$($LogFile)") | Out-Null | |
$Sources.Rows.Add("E:\Profiles\","/R:1 /W:1 /MT:128 /XD ""Profile.V2"" ""Profile.V5"" ""Profile.V6"" /LOG+:$($LogFile)") | Out-Null | |
$Sources.Rows.Add("E:\Backups\","/R:1 /W:1 /MT:32 /LOG+:$($LogFile)") | Out-Null | |
#Options for the command to run | |
$Cmd = "C:\Windows\system32\robocopy.exe" | |
$GlobalSwitches = "/MIR /SEC /NFL /NDL /XF "".DS_Store"" ""Desktop.ini""" | |
#Name or IP of Remote SMTP Server | |
$SMTPServer = "Company-co-nz.mail.protection.outlook.com" | |
#Server port (typically 25) | |
$SMTPPort = 25 | |
#Functions | |
function Add-DataSize ([String] $Val1,[string] $Val2) { | |
#Write-Host "Adding $($Val1) and $($Val2)" | |
$Val1Arr = $Val2Arr = @("0","b") | |
if ($Val1.Trim() -match "[0-9]+[.]*[0-9]*\s[b,k,m,g]"){ | |
#Number and unit with a space | |
$Val1Arr = $Val1.Trim().Split(" ",2) | |
} elseif ($Val1.Trim() -match "[0-9]+[.]*[0-9]*"){ | |
#Number only | |
$Val1Arr[0] = $Val1 | |
$Val1Arr[1] = "b" | |
} else { | |
Write-Host "$($Val1.Trim()) not a valid data size (i.e. number.number_unit)." | |
Return "0 b" | |
} | |
if ($Val2.Trim() -match "[0-9]+[.]*[0-9]*\s[b,k,m,g]"){ | |
#Number and unit with a space | |
$Val2Arr = $Val2.Trim().Split(" ",2) | |
} elseif ($Val2.Trim() -match "[0-9]+[.]*[0-9]*"){ | |
#Number only | |
$Val2Arr[0] = $Val2 | |
$Val2Arr[1] = "b" | |
} else { | |
Write-Host "$($Val2.Trim()) not a valid data size (i.e. number.number_unit)." | |
Return "0 b" | |
} | |
switch ($Val1Arr[1]){ | |
"k" {$Val1 = [double]$Val1Arr[0]*1024} | |
"m" {$Val1 = [double]$Val1Arr[0]*1024*1024} | |
"g" {$Val1 = [double]$Val1Arr[0]*1024*1024*1024} | |
default {$Val1 = [double]$Val1Arr[0]} | |
} | |
switch ($Val2Arr[1]){ | |
"k" {$Val2 = [double]$Val2Arr[0]*1024} | |
"m" {$Val2 = [double]$Val2Arr[0]*1024*1024} | |
"g" {$Val2 = [double]$Val2Arr[0]*1024*1024*1024} | |
default {$Val2 = [double]$Val2Arr[0]} | |
} | |
$DblResult = [double]$Val1 + [double]$Val2 | |
#Write-Host " - Result is $($DblResult) bytes" | |
if ($DblResult -gt (1024*1024*1024)){ | |
return [String]([Math]::Round($DblResult/1024/1024/1024,2))+" g" | |
} elseif ($DblResult -gt (1024*1024)){ | |
return [String]([Math]::Round($DblResult/1024/1024,2))+" m" | |
} elseif ($DblResult -gt (1024)){ | |
return [String]([Math]::Round($DblResult/1024,2))+" k" | |
} else { | |
return [String]([Math]::Round($DblResult,2))+" b" | |
} | |
} | |
function Convert-DatatableHtml ( $dt) { | |
$html = "<table border='1' style='width:80%;text-align: right;'>"; | |
$hmtl +="<tr>" | |
for($i = 0;$i -lt $dt.Columns.Count;$i++) { | |
$html += "<th>"+$dt.Columns[$i].ColumnName+"</th>" | |
} | |
$html +="</tr>" | |
for($i=0;$i -lt $dt.Rows.Count; $i++) { | |
$hmtl +="<tr>" | |
for($j=0; $j -lt $dt.Columns.Count; $j++) { | |
$html += "<td>"+$dt.Rows[$i][$j].ToString()+"</td>" | |
} | |
$html +="</tr>" | |
} | |
$html += "</table>" | |
return $html | |
} | |
function Get-Holiday ([DateTime]$Date, [String]$Region){ | |
#Initalise Variable | |
$HolidayName = $null | |
#Grab iCal file | |
$Content = Invoke-WebRequest "http://apps.employment.govt.nz/ical/public-holidays-all.ics" | |
#Error Checking | |
if(-not ($Content.Content.Substring(0,100) -like "BEGIN:VCALENDAR*")){ | |
Write-Error "Error recieving Holiday iCal from employment.govt.nz" | |
} | |
#Split the iCal content into individual events | |
$Events = $Content.Content -split 'BEGIN:VEVENT' | |
#Process each event | |
ForEach ($Event in $Events) { | |
#Check if the "DTSTART" matches the date we're checking | |
If(($Event -split '\r?\n') -contains "DTSTART;VALUE=DATE:$(Get-Date -Date $Date -UFormat '%Y%m%d')") { | |
#Update the HolidayName variable with the "SUMMARY" if the dates match | |
$HolidayName = ((($Event -split '\r?\n') | Select-String -Pattern "SUMMARY") -split ':')[1] | |
} | |
} | |
#If nothing found on that date, return null | |
If(-not $HolidayName){ | |
return $null | |
} | |
If ($Region -and $HolidayName -like "*Anniversary*"){ | |
#If a region is supplied, and the Holiday found is a regional anniversary, only return the correct region | |
If ($HolidayName -like "*$($Region)*"){ | |
return $HolidayName | |
} else { | |
return $null | |
} | |
} else { | |
#No Region supplied or the holiday found is not a regional anniversary, return the holday name | |
return $HolidayName | |
} | |
} | |
#If you use BitLocker to encrypt the destination filesystem, add/adjust calls to manage-bde here to unlock all possible destinations | |
Write-Host "Unlocking F:" | |
Start-Process -Wait -NoNewWindow -FilePath manage-bde -ArgumentList "-unlock F: -recoverypassword xxxxxxxxxxxxxxxxxxxxxxxxxxxx" | |
Start-Process -Wait -NoNewWindow -FilePath manage-bde -ArgumentList "-unlock F: -recoverypassword xxxxxxxxxxxxxxxxxxxxxxxxxxxx" | |
$ReturnCode = 0 | |
If(Test-Path $Dest){ | |
ForEach ($Source in $Sources){ | |
Write-Host "Backing up $($Source['Path'])" | |
$Return = Start-Process -PassThru -NoNewWindow -Wait -FilePath $Cmd -ArgumentList "$($Source['Path']) $($Dest.split(':')[0]):$($Source['Path'].split(':')[1]) $($GlobalSwitches) $($Source['Switches'])" | |
Write-Debug "Running: $($Cmd) $($Source['Path']) $($Dest.split(':')[0]):$($Source['Path'].split(':')[1]) $($GlobalSwitches) $($Source['Switches'])`nResult: $($Return.ExitCode)" | |
$ReturnCode = [math]::max($ReturnCode , $Return.ExitCode) | |
} | |
}else{ | |
$ReturnCode = 99 | |
} | |
$DriveLabel = (Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceId='$($Dest.split('\')[0])'" -Computer '.').VolumeName | |
$LogSummary = "Destination disk - <b> $($DriveLabel) </b></br>" | |
#Safely remove the backup drive ready for replacement on weekdays before 5 PM | |
If (-not (Get-Holiday -Date (Get-Date) -Region $Region) -And ([string](get-date).DayOfWeek).Substring(0,1) -ne "S" -And (get-date).Hour -lt 17) { | |
Write-Host "Preparing Drive for removal" | |
$Return = Start-Process -PassThru -NoNewWindow -Wait -FilePath "$(split-path -parent $MyInvocation.MyCommand.Definition)\RemoveDrive.exe" -ArgumentList $Dest -ErrorAction SilentlyContinue | |
Write-Debug "Command for removal: $(split-path -parent $MyInvocation.MyCommand.Definition)\RemoveDrive.exe $($Dest)" | |
$LogSummary = "$($LogSummary) </br>Device prepared for safe removal</br>" | |
If ($Return.ExitCode -ne 0){ | |
Write-Host "Safe removal failed. Error: $($Return.ExitCode)" | |
$LogSummary = "$($LogSummary) ERROR ($($Return.ExitCode)) Removing device</br>" | |
} | |
} | |
#Get Log Summary | |
Write-Host "Parsing the LogFile" | |
$StartTime = "" | |
$EndTime = "" | |
$Summary = New-Object System.Data.DataTable | |
#Total,Copied,Skipped,Mismatch,FAILED,Extras | |
$Summary.Columns.Add("Type","string") | Out-Null | |
$Summary.Columns.Add("Total","string") | Out-Null | |
$Summary.Columns.Add("Copied","string") | Out-Null | |
$Summary.Columns.Add("Skipped","string") | Out-Null | |
$Summary.Columns.Add("Mismatch","string") | Out-Null | |
$Summary.Columns.Add("FAILED","string") | Out-Null | |
$Summary.Columns.Add("Extras","string") | Out-Null | |
$Summary.Rows.Add("Dirs","0","0","0","0","0","0") | Out-Null | |
$Summary.Rows.Add("Files","0","0","0","0","0","0") | Out-Null | |
$Summary.Rows.Add("Bytes","0 b","0 b","0 b","0 b","0 b","0 b") | Out-Null | |
$LogSummary = $LogSummary+"</br>Results are:</br>`n" | |
foreach ($Line in Get-Content $LogFile) { | |
if($Line -like " Started :*"){ | |
If($StartTime -eq ""){$StartTime = $Line.split(":",2)[1]} | |
#}elseif($Line -like " Dest :*"){ | |
#}elseif($Line -like " Files :*"){ | |
}elseif($Line -like " Dirs :*"){ | |
$Line = $Line.split(':',2)[1] | |
$Summary.Rows[0].Total = [int]$Summary.Rows[0].Total + [int]$Line.Substring(0,10).Trim() | |
$Summary.Rows[0].Copied = [int]$Summary.Rows[0].Copied + [int]$Line.Substring(10,10).Trim() | |
$Summary.Rows[0].Skipped = [int]$Summary.Rows[0].Skipped + [int]$Line.Substring(20,10).Trim() | |
$Summary.Rows[0].Mismatch = [int]$Summary.Rows[0].Mismatch + [int]$Line.Substring(30,10).Trim() | |
$Summary.Rows[0].FAILED = [int]$Summary.Rows[0].FAILED + [int]$Line.Substring(40,10).Trim() | |
$Summary.Rows[0].Extras = [int]$Summary.Rows[0].Extras + [int]$Line.Substring(50,10).Trim() | |
}elseif($Line -like " Files :*"){ | |
$Line = $Line.split(':',2)[1] | |
$Summary.Rows[1].Total = [int]$Summary.Rows[1].Total + [int]$Line.Substring(0,10).Trim() | |
$Summary.Rows[1].Copied = [int]$Summary.Rows[1].Copied + [int]$Line.Substring(10,10).Trim() | |
$Summary.Rows[1].Skipped = [int]$Summary.Rows[1].Skipped + [int]$Line.Substring(20,10).Trim() | |
$Summary.Rows[1].Mismatch = [int]$Summary.Rows[1].Mismatch + [int]$Line.Substring(30,10).Trim() | |
$Summary.Rows[1].FAILED = [int]$Summary.Rows[1].FAILED + [int]$Line.Substring(40,10).Trim() | |
$Summary.Rows[1].Extras = [int]$Summary.Rows[1].Extras + [int]$Line.Substring(50,10).Trim() | |
}elseif($Line -like " Bytes :*"){ | |
$Line = $Line.split(':',2)[1] | |
$Summary.Rows[2].Total = Add-DataSize $Summary.Rows[2].Total $Line.Substring(0,10).Trim() | |
$Summary.Rows[2].Copied = Add-DataSize $Summary.Rows[2].Copied $Line.Substring(10,10).Trim() | |
$Summary.Rows[2].Skipped = Add-DataSize $Summary.Rows[2].Skipped $Line.Substring(20,10).Trim() | |
$Summary.Rows[2].Mismatch = Add-DataSize $Summary.Rows[2].Mismatch $Line.Substring(30,10).Trim() | |
$Summary.Rows[2].FAILED = Add-DataSize $Summary.Rows[2].FAILED $Line.Substring(40,10).Trim() | |
$Summary.Rows[2].Extras = Add-DataSize $Summary.Rows[2].Extras $Line.Substring(50,10).Trim() | |
#}elseif($Line -like " Times :*"){ | |
}elseif($Line -like " Ended :*"){ | |
$EndTime = $Line.split(":",2)[1] | |
}elseif($Line -like "*ERROR*" -And $ReturnCode -ne 99){ | |
$LogErrors = $LogErrors+$Line+"</br>" | |
} | |
} | |
$LogSummary += "<b>Job Started:</b> $($StartTime)</br>" | |
$LogSummary += "<b>Job Finished:</b> $($EndTime)</br></br>" | |
$LogSummary += Convert-DatatableHtml $Summary | |
#Build message body | |
$HTMLmsg = "<HTML>"+` | |
"<HEAD>"+` | |
"<BODY>"+` | |
"<B>DATASERVER Backup</B></br>" | |
If ($ReturnCode -eq 99){ | |
$HTMLmsg = "$($HTMLmsg)Backup Status: <font color=""red""><b>ERROR ($($ReturnCode))</b></font></br>" +` | |
"<b>Serious error. Could not find backup drive $($Dest)</b></br>" | |
}elseIf ($ReturnCode -gt 15){ | |
$HTMLmsg = "$($HTMLmsg)Backup Status: <font color=""red""><b>ERROR ($($ReturnCode))</b></font></br>" +` | |
"<i>Serious error. Robocopy did not copy any files.</br>" +` | |
"Either a usage error or an error due to insufficient access privileges</br>" +` | |
"on the source or destination directories.</i></br></br>" | |
}elseif ($ReturnCode -gt 7){ | |
$HTMLmsg = "$($HTMLmsg)Backup Status: <font color=""orange""><b>WARNING ($($ReturnCode))</b></font></br>" +` | |
"<i>Some files or directories could not be copied</br>" +` | |
"(copy errors occurred and the retry limit was exceeded).</br>" +` | |
"Check these errors further.</i></br></br>" | |
}elseif ($ReturnCode -gt 3){ | |
$HTMLmsg = "$($HTMLmsg)Backup Status: <font color=""orange""><b>WARNING ($($ReturnCode))</b></font></br>" +` | |
"<i>Some Mismatched files or directories were detected.</br>" +` | |
"Examine the output log. Some housekeeping may be needed.</i></br></br>" | |
}elseif ($ReturnCode -gt 1){ | |
$HTMLmsg = "$($HTMLmsg)Backup Status: <font color=""green""><b>SUCCESS ($($ReturnCode))</b></font></br>" +` | |
"<i>Some Extra files or directories were detected.</br>" +` | |
"Examine the output log for details.</i></br></br>" | |
}elseif ($ReturnCode -gt 0){ | |
$HTMLmsg = "$($HTMLmsg)Backup Status: <font color=""green""><b>SUCCESS ($($ReturnCode))</b></font></br>"+` | |
"<i>One or more files were copied successfully (that is, new files have arrived).</i></br></br>" | |
}else{ | |
$HTMLmsg = "$($HTMLmsg)Backup Status: <font color=""green""><b>SUCCESS ($($ReturnCode))</b></font></br>"+` | |
"<i>No errors occurred, and no copying was done.</br>" +` | |
"The source and destination directory trees are completely synchronized.</i></br></br>" | |
} | |
$HTMLmsg = "$($HTMLmsg)Robocopy log file can be found in $($LogFile)</br></br>$($LogSummary)</br></br><i>$($LogErrors)</i>"+` | |
"</BODY>"+` | |
"</HTML>" | |
Write-Host "Output: $($HTMLmsg.Replace("</br>","`n"))" | |
#Email the report | |
Send-MailMessage -To $ReportTo -Subject "SERVER Robocopy Backup" -From "[email protected]" -BodyAsHtml $HTMLmsg -SmtpServer $SMTPServer -Port $SMTPPort |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment