Skip to content

Instantly share code, notes, and snippets.

@kiwi-cam
Last active February 4, 2019 22:09
Show Gist options
  • Save kiwi-cam/24cb97a68b6b60c0c795dd8df4948812 to your computer and use it in GitHub Desktop.
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.
[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