2 # Script for rotating Drive Snapshot backups monthly
4 # Author: Patrick Canterino <patrick@patrick-canterino.de>
5 # WWW: https://www.patrick-canterino.de/
6 # https://github.com/pcanterino/dsmonrot
7 # License: 2-Clause BSD License
9 # Drive Snapshot is copyright by Tom Ehlert
10 # http://www.drivesnapshot.de/
14 # Path to backup directory
15 # This directory MUST exist, it is not created automatically!
16 [String]$backupDir = "Z:\"
17 # Disks to backup, see http://www.drivesnapshot.de/en/commandline.htm
18 [String]$disksToBackup = "D1:1"
19 # Path to Drive Snapshot
20 [String]$dsPath = "C:\Users\Patrick\Desktop\DSMonRot\snapshot.exe"
21 # Keep backups for this amount of months (excluding the current month),
23 [Int32]$keepMonths = 2
24 # Rotate BEFORE the beginning of a full backup (default is after a successful
26 # WARNING: If this option is set to $True and the full backup fails you could
28 [Boolean]$rotateBeforeBackup = $False
29 # Set to $True if you want to allow multiple backups for a day
30 [Boolean]$multipleDailyBackups = $False
31 # Path to Drive Snapshot log file (specify only the file name if you set
32 # $dsLogFileToBackup to $True)
33 #[String]$dsLogFile = "C:\Users\Patrick\Desktop\DSMonRot\snapshot.log"
34 [String]$dsLogFile = "snapshot.log"
35 # Set to $True if you want to put the log file of Drive Snapshot into the same
36 # directory as the backup
37 [Boolean]$dsLogFileToBackup = $True
38 # Path to directory where DSMonRot stores the log files
39 # Every month a new log file is created
40 [String]$logDir = "$PSScriptRoot\log"
41 # Keep log files for this amount of months (excluding the current month),
42 # 0 or less for indefinite
43 # You should set this to at least the same as $keepMonths
46 # Map network share to this drive letter, comment out if you don't want to use it
47 [String]$smbDrive = "Z"
48 # Path to network share
49 [String]$smbPath = "\\192.168.0.3\ds"
50 # User and password for connecting to network share, comment out if you don't want to use it
51 # (for example if you want to pass current Windows credentials)
52 [String]$smbUser = "patrick"
53 [String]$smbPassword = ""
55 # Send an email if an error occured
56 [Boolean]$emailOnError = $True
57 # From address of email notification
58 [String]$emailFromAddress = "alarm@test.local"
59 # To address of email notification
60 [String]$emailToAddress = "patrick@test.local"
61 # Subject of email notification
62 [String]$emailSubject = "DSMonRot on $env:computername"
64 [String]$emailMailserver = "localhost"
66 [Int32]$emailPort = 25
68 [Boolean]$emailSSL = $False
70 [Boolean]$emailAuth = $False
72 [String]$emailUser = ""
74 [String]$emailPassword = ""
80 Write-Log writes a message to a specified log file with the current time stamp.
82 The Write-Log function is designed to add logging capability to other scripts.
83 In addition to writing output and/or verbose you can write to a log file for
86 Created by: Jason Wasser @wasserja
87 Modified: 11/24/2015 09:30:19 AM
90 * Code simplification and clarification - thanks to @juneb_get_help
91 * Added documentation.
92 * Renamed LogPath parameter to Path to keep it standard - thanks to @JeffHicks
93 * Revised the Force switch to work as it should - thanks to @JeffHicks
96 * Add error handling if trying to create a log file in a inaccessible location.
97 * Add ability to write $Message to $Verbose or $Error pipelines to eliminate
100 Message is the content that you wish to add to the log file.
102 The path to the log file to which you would like to write. By default the function will
103 create the path and file if it does not exist.
105 Specify the criticality of the log information being written to the log (i.e. Error, Warning, Informational)
107 Use NoClobber if you do not wish to overwrite an existing file.
109 Write-Log -Message 'Log message'
110 Writes the message to c:\Logs\PowerShellLog.log.
112 Write-Log -Message 'Restarting Server.' -Path c:\Logs\Scriptoutput.log
113 Writes the content to the specified log file and creates the path and file specified.
115 Write-Log -Message 'Folder does not exist.' -Path c:\Logs\Script.log -Level Error
116 Writes the message to the specified log file as an error message, and writes the message to the error pipeline.
118 https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0
125 [Parameter(Mandatory=$true,
126 ValueFromPipelineByPropertyName=$true)]
127 [ValidateNotNullOrEmpty()]
128 [Alias("LogContent")]
131 [Parameter(Mandatory=$false)]
133 [string]$Path='C:\Logs\PowerShellLog.log',
135 [Parameter(Mandatory=$false)]
136 [ValidateSet("Error","Warn","Info")]
137 [string]$Level="Info",
139 [Parameter(Mandatory=$false)]
145 # Set VerbosePreference to Continue so that verbose messages are displayed.
146 $VerbosePreference = 'Continue'
151 # If the file already exists and NoClobber was specified, do not write to the log.
152 if ((Test-Path $Path) -AND $NoClobber) {
153 Write-Error "Log file $Path already exists, and you specified NoClobber. Either delete the file or specify a different name."
157 # If attempting to write to a log file in a folder/path that doesn't exist create the file including the path.
158 elseif (!(Test-Path $Path)) {
159 Write-Verbose "Creating $Path."
160 $NewLogFile = New-Item $Path -Force -ItemType File
164 # Nothing to see here yet.
167 # Format Date for our Log File
168 $FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
170 # Write message to error, warning, or verbose pipeline and specify $LevelText
174 $LevelText = 'ERROR:'
177 Write-Warning $Message
178 $LevelText = 'WARNING:'
181 Write-Verbose $Message
186 # Write log entry to $Path
187 "$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append
194 function Send-Email([String]$body) {
196 # Allow SMTP with SSL and SMTP Auth
197 # see: http://petermorrissey.blogspot.de/2013/01/sending-smtp-emails-with-powershell.html
199 $smtp = New-Object System.Net.Mail.SmtpClient($emailMailServer, $emailPort)
201 $smtp.EnableSSL = $emailSSL
204 $smtp.Credentials = New-Object System.Net.NetworkCredential($emailUser, $emailPassword)
207 $smtp.Send($emailFromAddress, $emailToAddress, $emailSubject, $body)
210 Write-Log "Could not send email: $_.Exception.Message" -Path $logFile -Level Error
214 function Rotate-Backup {
215 if($keepMonths -lt 0) {
219 $keepMonthsCount = $keepMonths
221 Get-ChildItem $backupDir -Directory | Where-Object {($_.Name -ne $currMonth) -and ($_.Name -match "^\d{4,}-\d{2}$")} | Sort-Object -Descending |
223 if($keepMonthsCount -ge 0) {
227 if($keepMonthsCount -eq -1) {
228 Write-Log "Deleting backup $($_.FullName)" -Path $logFile -Level Info
229 Remove-Item -Recurse -Force $_.FullName
234 function Rotate-Log {
235 if($keepLogs -le 0) {
239 $keepLogsCount = $keepLogs
241 Get-ChildItem $logDir -File | Where-Object {($_.Name -ne "$currMonth.log") -and ($_.Name -match "^\d{4,}-\d{2}\.log$")} | Sort-Object -Descending |
243 if($keepLogsCount -ge 0) {
247 if($keepLogsCount -eq -1) {
248 Write-Log "Deleting log file $($_.FullName)" -Path $logFile -Level Info
249 Remove-Item -Force $_.FullName
254 $dsAdditionalArgs = @("--UseVSS")
258 $smbConnected = $False
263 $currMonth = Get-Date -format "yyyy-MM"
264 $currDay = Get-Date -format "yyyy-MM-dd"
265 $currTime = Get-Date -format "HH-mm-ss" # no colon because we need this for a directory name
267 # Check if the directory for the log files exists and create it if necessary
268 if(!(Test-Path $logDir)) {
270 New-Item -ItemType directory -Path $logDir -ErrorAction Stop | Out-Null
273 Write-Error "Could not create log directory $logDir`: $_.Exception.Message"
274 $errorMessages += "Could not create log directory $logDir`: $_.Exception.Message"
278 $logFile = "$logDir\$currMonth.log"
280 # Continue only if the log directory exists or if it was created successfully (no error message added)
281 if($errorMessages.Count -eq 0) {
282 $startTime = Get-Date -format "yyyy-MM-dd HH:mm:ss"
283 Write-Log "Started at $startTime" -Path $logFile
285 # Connect the network drive if necessary
287 Write-Log "Connecting network drive $smbDrive to $smbPath" -Path $logFile
290 if($smbUser -and $smbPassword) {
291 Write-Log "Connecting to network drive with credentials" -Path $logFile
293 $secSmbPassword = $smbPassword | ConvertTo-SecureString -asPlainText -Force
294 $smbCredential = New-Object System.Management.Automation.PSCredential($smbUser, $secSmbPassword)
296 New-PSDrive -Name $smbDrive -PSProvider "FileSystem" -Root $smbPath -Credential $smbCredential -Persist -ErrorAction Stop | Out-Null
299 Write-Log "Connecting to network drive without credentials" -Path $logFile
301 New-PSDrive -Name $smbDrive -PSProvider "FileSystem" -Root $smbPath -Persist -ErrorAction Stop | Out-Null
304 $smbConnected = $True
307 Write-Log "Could not connect to network drive $smbDrive`: $_.Exception.Message" -Path $logFile -Level Error
308 $errorMessages += "Could not connect to network drive $smbDrive`: $_.Exception.Message"
312 # Check if the backup directory exists
313 if(!(Test-Path $backupDir)) {
314 Write-Log "Directory $backupDir does not exist!" -Path $logFile -Level Error
315 $errorMessages += "Directory $backupDir does not exist!"
318 # Continue only if no error message was recorded (i.e. backup directory does not exist)
319 if($errorMessages.Count -eq 0) {
320 # Compose the backup target directories
321 $backupTarget = $backupDir + "\" + $currMonth
322 $backupTargetFull = $backupTarget + "\Full"
324 $backupTargetDiff = $backupTarget + "\Diff-" + $currDay
326 if($multipleDailyBackups) {
327 $backupTargetDiff = $backupTargetDiff + "-" + $currTime
330 # Check if the backup target for this month, the directory for the full backup
331 # and the hash files exists. In this case we do a differential backup.
332 if((Test-Path $backupTarget) -and (Test-Path $backupTargetFull) -and (Test-Path "$backupTargetFull\*.hsh")) {
333 # Do a differential backup
335 Write-Log "Doing a differential backup" -Path $logFile
339 if(!(Test-Path $backupTargetDiff)) {
341 New-Item -ItemType directory -Path $backupTargetDiff -ErrorAction Stop | Out-Null
344 Write-Log "Could not create directory $backupTargetDiff`: $_.Exception.Message" -Path $logFile -Level Error
345 $errorMessages += "Could not create directory $backupTargetDiff`: $_.Exception.Message"
348 if($errorMessages.Count -eq 0) {
349 $dsLogPath = if($dsLogFileToBackup) { "$backupTargetDiff\$dsLogFile" } else { $dsLogFile }
351 $dsArgs = @($disksToBackup, "--logfile:$dsLogPath", "$backupTargetDiff\`$disk.sna", "-h$backupTargetFull\`$disk.hsh") + $dsAdditionalArgs
352 $dsCommand = "$dsPath $dsArgs"
354 Write-Log $dsCommand -Path $logFile
358 if($LastExitCode -ne 0) {
359 Write-Log "Drive Snapshot failed to backup! Exit code: $LastExitCode" -Path $logFile -Level Error
360 $errorMessages += "Drive Snapshot failed to backup! Exit code: $LastExitCode"
363 Write-Log "Drive Snapshot succeeded!" -Path $logFile
369 Write-Log "Directory $backupTargetDiff already exists!" -Path $logFile -Level Error
370 $errorMessages += "Directory $backupTargetDiff already exists!"
376 Write-Log "Doing a full backup" -Path $logFile
378 if(!(Test-Path $backupTarget)) {
380 New-Item -ItemType directory -Path $backupTarget -ErrorAction Stop | Out-Null
383 Write-Log "Could not create directory $backupTarget`: $_.Exception.Message" -Path $logFile -Level Error
384 $errorMessages += "Could not create directory $backupTarget`: $_.Exception.Message"
388 if($errorMessages.Count -eq 0) {
389 if(!(Test-Path $backupTargetFull)) {
391 New-Item -ItemType directory -Path $backupTargetFull -ErrorAction Stop | Out-Null
394 Write-Log "Could not create directory $backupTargetFull`: $_.Exception.Message" -Path $logFile -Level Error
395 $errorMessages += "Could not create directory $backupTargetFull`: $_.Exception.Message"
399 if($errorMessages.Count -eq 0) {
400 if($rotateBeforeBackup) {
404 $dsLogPath = if($dsLogFileToBackup) { "$backupTargetFull\$dsLogFile" } else { $dsLogFile }
406 $dsArgs = @($disksToBackup, "--logfile:$dsLogPath", "$backupTargetFull\`$disk.sna") + $dsAdditionalArgs
407 $dsCommand = "$dsPath $dsArgs"
409 Write-Log $dsCommand -Path $logFile
413 if($LastExitCode -ne 0) {
414 Write-Log "Drive Snapshot failed to backup! Exit code: $LastExitCode" -Path $logFile -Level Error
415 $errorMessages += "Drive Snapshot failed to backup! Exit code: $LastExitCode"
418 Write-Log "Drive Snapshot succeeded!" -Path $logFile
422 if($rotateBeforeBackup -eq $False -and $success -eq $True) {
430 # Disconnect the network drive if necessary
432 Write-Log "Disconnecting network drive" -Path $logFile
435 Remove-PSDrive $smbDrive -ErrorAction Stop
438 Write-Log "Could not disconnect network drive $smbDrive`: $_.Exception.Message" -Path $logFile -Level Error
439 $errorMessages += "Could not disconnect network drive $smbDrive`: $_.Exception.Message"
443 # Rotate the log files
447 # If there was any error message recorded, send a mail if configured
448 if($emailOnError -and $errorMessages.Count -gt 0) {
449 $emailBody = "This is DSMonRot on $env:computername, started at $startTime.`n"
450 $emailBody += "An error occured while performing a backup. Below are the error messages and some status information.`n`n"
451 $emailBody += "Backup directory: $backupDir`n"
452 $emailBody += "Log directory: $logDir`n"
453 $emailBody += "Current log file: $logFile`n"
454 $emailBody += "Differential backup: $isDiff`n"
455 $emailBody += "Backup successful: $success`n"
456 $emailBody += "Drive Snapshot command: $dsCommand`n`n"
457 $emailBody += ($errorMessages -join "`n")
459 Send-Email $emailBody
462 $endTime = Get-Date -format "yyyy-MM-dd HH:mm:ss"
463 Write-Log "Ended at $endTime" -Path $logFile