X-Git-Url: https://git.p6c8.net/dsmonrot.git/blobdiff_plain/bca3ccac96405fb70f0bf68ae4bf19233e295971..01bab7731e910390ae331b24995b429a86789ea6:/dsmonrot.ps1

diff --git a/dsmonrot.ps1 b/dsmonrot.ps1
index b838d75..7012cf1 100644
--- a/dsmonrot.ps1
+++ b/dsmonrot.ps1
@@ -12,17 +12,36 @@
 # Config
 
 # Path to backup directory
+# This directory MUST exist, it is not created automatically!
 [String]$backupDir = "Z:\"
-# Keep backup for this amount of months (excluding the current month), -1 for indefinite
-[Int32]$keepMonths = 2
-# Path to Drive Snapshot
-[String]$dsPath = "C:\Users\Patrick\Desktop\DSMonRot\snapshot.exe"
-# Path to Drive Snapshot log file
-[String]$dsLogFile = "C:\Users\Patrick\Desktop\DSMonRot\snapshot.log"
 # Disks to backup, see http://www.drivesnapshot.de/en/commandline.htm
 [String]$disksToBackup = "HD1:1"
-# Path to DSMonRot log file
-[String]$logFile = "C:\Users\Patrick\Desktop\DSMonRot\dsmonrot.log"
+# Path to Drive Snapshot
+[String]$dsPath = "C:\Users\Patrick\Desktop\DSMonRot\snapshot.exe"
+# Keep backups for this amount of months (excluding the current month),
+# -1 for indefinite
+[Int32]$keepMonths = 2
+# Rotate BEFORE the beginning of a full backup (default is after a successful
+# full backup)
+# WARNING: If this option is set to $True and the full backup fails you could
+# have NO backup
+[Boolean]$rotateBeforeBackup = $False
+# Set to $True if you want to allow multiple backups for a day
+[Boolean]$multipleDailyBackups = $False
+# Path to Drive Snapshot log file (specify only the file name if you set
+# $dsLogFileToBackup to $True)
+#[String]$dsLogFile = "C:\Users\Patrick\Desktop\DSMonRot\snapshot.log"
+[String]$dsLogFile = "snapshot.log"
+# Set to $True if you want to put the log file of Drive Snapshot into the same
+# directory as the backup
+[Boolean]$dsLogFileToBackup = $True
+# Path to directory where DSMonRot stores the log files
+# Every month a new log file is created
+[String]$logDir = "$PSScriptRoot\log"
+# Keep log files for this amount of months (excluding the current month),
+# 0 or less for indefinite
+# You should set this to at least the same as $keepMonths
+[Int32]$keepLogs = 2
 
 # Map network share to this drive letter, comment out if you don't want to use it
 [String]$smbDrive = "Z"
@@ -40,7 +59,7 @@
 # To address of email notification
 [String]$emailToAddress = "patrick@test.local"
 # Subject of email notification
-[String]$emailSubject = "DSMonRot"
+[String]$emailSubject = "DSMonRot on $env:computername"
 # Mail server
 [String]$emailMailserver = "localhost"
 # SMTP Port
@@ -56,164 +75,389 @@
 
 # End of config
 
-$dsAdditionalArgs = @("--logfile:$dsLogFile", "--UseVSS")
+<# 
+.Synopsis 
+   Write-Log writes a message to a specified log file with the current time stamp. 
+.DESCRIPTION 
+   The Write-Log function is designed to add logging capability to other scripts. 
+   In addition to writing output and/or verbose you can write to a log file for 
+   later debugging. 
+.NOTES 
+   Created by: Jason Wasser @wasserja 
+   Modified: 11/24/2015 09:30:19 AM   
+ 
+   Changelog: 
+    * Code simplification and clarification - thanks to @juneb_get_help 
+    * Added documentation. 
+    * Renamed LogPath parameter to Path to keep it standard - thanks to @JeffHicks 
+    * Revised the Force switch to work as it should - thanks to @JeffHicks 
+ 
+   To Do: 
+    * Add error handling if trying to create a log file in a inaccessible location. 
+    * Add ability to write $Message to $Verbose or $Error pipelines to eliminate 
+      duplicates. 
+.PARAMETER Message 
+   Message is the content that you wish to add to the log file.  
+.PARAMETER Path 
+   The path to the log file to which you would like to write. By default the function will  
+   create the path and file if it does not exist.  
+.PARAMETER Level 
+   Specify the criticality of the log information being written to the log (i.e. Error, Warning, Informational) 
+.PARAMETER NoClobber 
+   Use NoClobber if you do not wish to overwrite an existing file. 
+.EXAMPLE 
+   Write-Log -Message 'Log message'  
+   Writes the message to c:\Logs\PowerShellLog.log. 
+.EXAMPLE 
+   Write-Log -Message 'Restarting Server.' -Path c:\Logs\Scriptoutput.log 
+   Writes the content to the specified log file and creates the path and file specified.  
+.EXAMPLE 
+   Write-Log -Message 'Folder does not exist.' -Path c:\Logs\Script.log -Level Error 
+   Writes the message to the specified log file as an error message, and writes the message to the error pipeline. 
+.LINK 
+   https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0 
+#>
+function Write-Log 
+{ 
+    [CmdletBinding()] 
+    Param 
+    ( 
+        [Parameter(Mandatory=$true, 
+                   ValueFromPipelineByPropertyName=$true)] 
+        [ValidateNotNullOrEmpty()] 
+        [Alias("LogContent")] 
+        [string]$Message, 
+ 
+        [Parameter(Mandatory=$false)] 
+        [Alias('LogPath')] 
+        [string]$Path='C:\Logs\PowerShellLog.log', 
+         
+        [Parameter(Mandatory=$false)] 
+        [ValidateSet("Error","Warn","Info")] 
+        [string]$Level="Info", 
+         
+        [Parameter(Mandatory=$false)] 
+        [switch]$NoClobber 
+    ) 
+ 
+    Begin 
+    { 
+        # Set VerbosePreference to Continue so that verbose messages are displayed. 
+        $VerbosePreference = 'Continue' 
+    } 
+    Process 
+    { 
+         
+        # If the file already exists and NoClobber was specified, do not write to the log. 
+        if ((Test-Path $Path) -AND $NoClobber) { 
+            Write-Error "Log file $Path already exists, and you specified NoClobber. Either delete the file or specify a different name." 
+            Return 
+            } 
+ 
+        # If attempting to write to a log file in a folder/path that doesn't exist create the file including the path. 
+        elseif (!(Test-Path $Path)) { 
+            Write-Verbose "Creating $Path." 
+            $NewLogFile = New-Item $Path -Force -ItemType File 
+            } 
+ 
+        else { 
+            # Nothing to see here yet. 
+            } 
+ 
+        # Format Date for our Log File 
+        $FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" 
+ 
+        # Write message to error, warning, or verbose pipeline and specify $LevelText 
+        switch ($Level) { 
+            'Error' { 
+                Write-Error $Message 
+                $LevelText = 'ERROR:' 
+                } 
+            'Warn' { 
+                Write-Warning $Message 
+                $LevelText = 'WARNING:' 
+                } 
+            'Info' { 
+                Write-Verbose $Message 
+                $LevelText = 'INFO:' 
+                } 
+            } 
+         
+        # Write log entry to $Path 
+        "$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append 
+    } 
+    End 
+    { 
+    } 
+}
 
-# Allow SMTP with SSL and SMTP Auth
-# see: http://petermorrissey.blogspot.de/2013/01/sending-smtp-emails-with-powershell.html
 function Send-Email([String]$body) {
-	Write-Host "Sending email: $emailToAddress, $body"
-	
 	try {
-		$smtp = New-Object System.Net.Mail.SmtpClient($emailMailServer, $emailPort);
+		# Allow SMTP with SSL and SMTP Auth
+		# see: http://petermorrissey.blogspot.de/2013/01/sending-smtp-emails-with-powershell.html
+	
+		$smtp = New-Object System.Net.Mail.SmtpClient($emailMailServer, $emailPort)
 
 		$smtp.EnableSSL = $emailSSL
 
 		if($emailAuth) {
-			$smtp.Credentials = New-Object System.Net.NetworkCredential($emailUser, $emailPassword);
+			$smtp.Credentials = New-Object System.Net.NetworkCredential($emailUser, $emailPassword)
 		}
 
-		$smtp.Send($emailFromAddress, $emailToAddress, $emailSubject, $body);
+		$smtp.Send($emailFromAddress, $emailToAddress, $emailSubject, $body)
 	}
 	catch {
-		Write-Host "Could not send email: $_.Exception.Message"
+		Write-Log "Could not send email: $_.Exception.Message" -Path $logFile -Level Error
 	}
 }
 
-$errorMessages = @()
-
-$smbConnected = $False
-$success = $False
-
-if($smbDrive) {
-	Try {
-		Write-Host "Connecting network drive"
+function Rotate-Backup {
+	if($keepMonths -lt 0) {
+		return
+	}
+	
+	$keepMonthsCount = $keepMonths
+	
+	Get-ChildItem $backupDir -Directory | Where-Object {($_.Name -ne $currMonth) -and ($_.Name -match "^\d{4,}-\d{2}$")} | Sort-Object -Descending |
+	Foreach-Object {
+		if($keepMonthsCount -ge 0) {
+			$keepMonthsCount--
+		}
 		
-		if($smbUser -and $smbPassword) {
-			Write-Host "With credentials"
-			
-			$secSmbPassword = $smbPassword | ConvertTo-SecureString -asPlainText -Force
-			$smbCredential = New-Object System.Management.Automation.PSCredential($smbUser, $secSmbPassword)
+		if($keepMonthsCount -eq -1) {
+			Write-Log "Deleting backup $($_.FullName)" -Path $logFile -Level Info
+			Remove-Item -Recurse -Force $_.FullName
+		}
+	}
+}
 
-			New-PSDrive -Name $smbDrive -PSProvider "FileSystem" -Root $smbPath -Credential $smbCredential -Persist -ErrorAction Stop
+function Rotate-Log {
+	if($keepLogs -le 0) {
+		return
+	}
+	
+	$keepLogsCount = $keepLogs
+	
+	Get-ChildItem $logDir -File | Where-Object {($_.Name -ne "$currMonth.log") -and ($_.Name -match "^\d{4,}-\d{2}\.log$")} | Sort-Object -Descending |
+	Foreach-Object {
+		if($keepLogsCount -ge 0) {
+			$keepLogsCount--
 		}
-		else {
-			Write-Host "Without credentials"
 		
-			New-PSDrive -Name $smbDrive -PSProvider "FileSystem" -Root $smbPath -Persist -ErrorAction Stop
+		if($keepLogsCount -eq -1) {
+			Write-Log "Deleting log file $($_.FullName)" -Path $logFile -Level Info
+			Remove-Item -Force $_.FullName
 		}
-		
-		$smbConnected = $True
-	}
-	Catch {
-		Write-Host "Could not connect to network drive: $_.Exception.Message"
-		exit
 	}
 }
 
-if(!(Test-Path $backupDir)) {
-	Write-Host "Directory $backupDir does not exist!"
-	exit
-}
+$dsAdditionalArgs = @("--UseVSS")
+
+$errorMessages = @()
+
+$smbConnected = $False
+$success = $False
+$isDiff = $False
+$dsCommand = ""
 
 $currMonth = Get-Date -format "yyyy-MM"
 $currDay = Get-Date -format "yyyy-MM-dd"
+$currTime = Get-Date -format "HH-mm-ss" # no colon because we need this for a directory name
 
-Write-Host $currMonth
-
-$backupTarget = $backupDir + "\" + $currMonth
-$backupTargetFull = $backupTarget + "\" + "Full"
+# Check if the directory for the log files exists and create it if necessary
+if(!(Test-Path $logDir)) {
+	try {
+		New-Item -ItemType directory -Path $logDir -ErrorAction Stop | Out-Null
+	}
+	catch {
+		Write-Error "Could not create log directory $logDir`: $_.Exception.Message"
+		$errorMessages += "Could not create log directory $logDir`: $_.Exception.Message"
+	}
+}
 
-$backupTargetDiff = $backupTarget + "\" + "Diff-" + $currDay
+$logFile = "$logDir\$currMonth.log"
 
-Write-Host $backupTarget
+# Continue only if the log directory exists or if it was created successfully (no error message added)
+if($errorMessages.Count -eq 0) {
+	$startTime = Get-Date -format "yyyy-MM-dd HH:mm:ss"
+	Write-Log "Started at $startTime" -Path $logFile
 
-$isDiff = $False
+	# Connect the network drive if necessary
+	if($smbDrive) {
+		Write-Log "Connecting network drive $smbDrive to $smbPath" -Path $logFile
 
-if((Test-Path $backupTarget) -and (Test-Path $backupTargetFull) -and (Test-Path "$backupTargetFull\*.hsh")) {
-	Write-Host "Differential backup"
-	
-	$isDiff = $True
-	
-	if(!(Test-Path $backupTargetDiff)) {
-		Write-Host "Creating directory $backupTargetDiff"
-		
 		try {
-			New-Item -ItemType directory -Path $backupTargetDiff -ErrorAction Stop
-		
-			$success = $True
+			if($smbUser -and $smbPassword) {
+				Write-Log "Connecting to network drive with credentials" -Path $logFile
+				
+				$secSmbPassword = $smbPassword | ConvertTo-SecureString -asPlainText -Force
+				$smbCredential = New-Object System.Management.Automation.PSCredential($smbUser, $secSmbPassword)
+
+				New-PSDrive -Name $smbDrive -PSProvider "FileSystem" -Root $smbPath -Credential $smbCredential -Persist -ErrorAction Stop | Out-Null
+			}
+			else {
+				Write-Log "Connecting to network drive without credentials" -Path $logFile
+			
+				New-PSDrive -Name $smbDrive -PSProvider "FileSystem" -Root $smbPath -Persist -ErrorAction Stop | Out-Null
+			}
+			
+			$smbConnected = $True
 		}
 		catch {
-			Write-Host "Could not create directory $backupTargetDiff`: $_.Exception.Message"
-			exit
+			Write-Log "Could not connect to network drive $smbDrive`: $_.Exception.Message" -Path $logFile -Level Error
+			$errorMessages += "Could not connect to network drive $smbDrive`: $_.Exception.Message"
 		}
+	}
+
+	# Check if the backup directory exists
+	if(!(Test-Path $backupDir)) {
+		Write-Log "Directory $backupDir does not exist!" -Path $logFile -Level Error
+		$errorMessages += "Directory $backupDir does not exist!"
+	}
+
+	# Continue only if no error message was recorded (i.e. backup directory does not exist)
+	if($errorMessages.Count -eq 0) {
+		# Compose the backup target directories
+		$backupTarget = $backupDir + "\" + $currMonth
+		$backupTargetFull = $backupTarget + "\Full"
+
+		$backupTargetDiff = $backupTarget + "\Diff-" + $currDay
 		
-		$dsArgs = @($disksToBackup, "$backupTargetDiff\`$disk.sna", "-h$backupTargetFull\`$disk.hsh") + $dsAdditionalArgs
-		Write-Host $dsPath ($dsArgs -join " ")
-		
-		& $dsPath $dsArgs
+		if($multipleDailyBackups) {
+			$backupTargetDiff = $backupTargetDiff + "-" + $currTime
+		}
+
+		# Check if the backup target for this month, the directory for the full backup
+		# and the hash files exists. In this case we do a differential backup.
+		if((Test-Path $backupTarget) -and (Test-Path $backupTargetFull) -and (Test-Path "$backupTargetFull\*.hsh")) {
+			# Do a differential backup
 		
-		if($LastExitCode -ne 0) {
-			Write-Host "Error code: $LastExitCode"
+			Write-Log "Doing a differential backup" -Path $logFile
+			
+			$isDiff = $True
+			
+			if(!(Test-Path $backupTargetDiff)) {
+				try {
+					New-Item -ItemType directory -Path $backupTargetDiff -ErrorAction Stop | Out-Null
+				}
+				catch {
+					Write-Log "Could not create directory $backupTargetDiff`: $_.Exception.Message" -Path $logFile -Level Error
+					$errorMessages += "Could not create directory $backupTargetDiff`: $_.Exception.Message"
+				}
+				
+				if($errorMessages.Count -eq 0) {
+					$dsLogPath = if($dsLogFileToBackup) { "$backupTargetDiff\$dsLogFile" } else { $dsLogFile }
+					
+					$dsArgs = @($disksToBackup, "--logfile:$dsLogPath", "$backupTargetDiff\`$disk.sna", "-h$backupTargetFull\`$disk.hsh") + $dsAdditionalArgs
+					$dsCommand = "$dsPath $dsArgs"
+					
+					Write-Log $dsCommand -Path $logFile
+					
+					& $dsPath $dsArgs
+					
+					if($LastExitCode -ne 0) {
+						Write-Log "Drive Snapshot failed to backup! Exit code: $LastExitCode" -Path $logFile -Level Error
+						$errorMessages += "Drive Snapshot failed to backup! Exit code: $LastExitCode"
+					}
+					else {
+						Write-Log "Drive Snapshot succeeded!" -Path $logFile
+						$success = $True
+					}
+				}
+			}
+			else {
+				Write-Log "Directory $backupTargetDiff already exists!" -Path $logFile -Level Error
+				$errorMessages += "Directory $backupTargetDiff already exists!"
+			}
 		}
-	}
-	else {
-		Write-Host "Directory $backupTargetDiff already exists!"
+		else {
+			# Do a full backup
 		
-		$errorMessages += "Directory $backupTargetDiff already exists!"
-	}
-}
-else {
-	Write-Host "Full backup"
-	
-	if(!(Test-Path $backupTarget)) {
-		Write-Host "Creating directory $backupTarget"
-		New-Item -ItemType directory -Path $backupTarget
-	}
-	
-	if(!(Test-Path $backupTargetFull)) {
-		Write-Host "Creating directory $backupTargetFull"
-		New-Item -ItemType directory -Path $backupTargetFull
-	}
+			Write-Log "Doing a full backup" -Path $logFile
 
-	$dsArgs = @($disksToBackup, "$backupTargetFull\`$disk.sna") + $dsAdditionalArgs
-	Write-Host $dsPath ($dsArgs -join " ")
-	
-	& $dsPath $dsArgs
-	
-	if($LastExitCode -ne 0) {
-		Write-Host "Error code: $LastExitCode"
+			if(!(Test-Path $backupTarget)) {
+				try {
+					New-Item -ItemType directory -Path $backupTarget -ErrorAction Stop | Out-Null
+				}
+				catch {
+					Write-Log "Could not create directory $backupTarget`: $_.Exception.Message" -Path $logFile -Level Error
+					$errorMessages += "Could not create directory $backupTarget`: $_.Exception.Message"
+				}
+			}
+			
+			if($errorMessages.Count -eq 0) {
+				if(!(Test-Path $backupTargetFull)) {
+					try {
+						New-Item -ItemType directory -Path $backupTargetFull -ErrorAction Stop | Out-Null
+					}
+					catch {
+						Write-Log "Could not create directory $backupTargetFull`: $_.Exception.Message" -Path $logFile -Level Error
+						$errorMessages += "Could not create directory $backupTargetFull`: $_.Exception.Message"
+					}
+				}
+				
+				if($errorMessages.Count -eq 0) {
+					if($rotateBeforeBackup) {
+						Rotate-Backup
+					}
+					
+					$dsLogPath = if($dsLogFileToBackup) { "$backupTargetFull\$dsLogFile" } else { $dsLogFile }
+
+					$dsArgs = @($disksToBackup, "--logfile:$dsLogPath", "$backupTargetFull\`$disk.sna") + $dsAdditionalArgs
+					$dsCommand = "$dsPath $dsArgs"
+					
+					Write-Log $dsCommand -Path $logFile
+					
+					& $dsPath $dsArgs
+					
+					if($LastExitCode -ne 0) {
+						Write-Log "Drive Snapshot failed to backup! Exit code: $LastExitCode" -Path $logFile -Level Error
+						$errorMessages += "Drive Snapshot failed to backup! Exit code: $LastExitCode"
+					}
+					else {
+						Write-Log "Drive Snapshot succeeded!" -Path $logFile
+						$success = $True
+					}
+					
+					if($rotateBeforeBackup -eq $False -and $success -eq $True) {
+						Rotate-Backup
+					}
+				}
+			}
+		}
 	}
-	
-	$success = $False
-}
 
-if($isDiff -eq $False -and $success -eq $True -and $keepMonths -ge 0) {
-	Write-Host "Rotating"
-	
-	$keepMonthsCount = $keepMonths
-	
-	Get-ChildItem $backupDir -Directory | Where-Object {($_.Name -ne $currMonth) -and ($_.Name -match "^\d{4,}-\d{2}$")} | Sort-Object -Descending |
-	Foreach-Object {
-		Write-Host $_ "=>" $_.FullName
+	# Disconnect the network drive if necessary
+	if($smbConnected) {
+		Write-Log "Disconnecting network drive" -Path $logFile
 		
-		if($keepMonthsCount -ge 0) {
-			$keepMonthsCount--
+		try {
+			Remove-PSDrive $smbDrive -ErrorAction Stop
 		}
-		
-		Write-Host $keepMonthsCount
-		
-		if($keepMonthsCount -eq -1) {
-			Write-Host "Deleting $_"
-			Remove-Item -Recurse -Force $_.FullName
+		catch {
+			Write-Log "Could not disconnect network drive $smbDrive`: $_.Exception.Message" -Path $logFile -Level Error
+			$errorMessages +=  "Could not disconnect network drive $smbDrive`: $_.Exception.Message"
 		}
 	}
+	
+	# Rotate the log files
+	Rotate-Log
 }
 
-if($smbConnected) {
-	Write-Host "Disconnecting network drive"
-	Remove-PSDrive $smbDrive
+# If there was any error message recorded, send a mail if configured
+if($emailOnError -and $errorMessages.Count -gt 0) {
+	$emailBody  = "This is DSMonRot on $env:computername, started at $startTime.`n"
+	$emailBody += "An error occured while performing a backup. Below are the error messages and some status information.`n`n"
+	$emailBody += "Backup directory:       $backupDir`n"
+	$emailBody += "Log directory:          $logDir`n"
+	$emailBody += "Current log file:       $logFile`n"
+	$emailBody += "Differential backup:    $isDiff`n"
+	$emailBody += "Backup successful:      $success`n"
+	$emailBody += "Drive Snapshot command: $dsCommand`n`n"
+	$emailBody += ($errorMessages -join "`n")
+
+	Send-Email $emailBody
 }
 
-if($emailOnError -and $errorMessages.Count -gt 0) {
-	Send-Email ("Error:`n"+($errorMessages -join "`n"))
-}
\ No newline at end of file
+$endTime = Get-Date -format "yyyy-MM-dd HH:mm:ss"
+Write-Log "Ended at $endTime" -Path $logFile
\ No newline at end of file