]> git.p6c8.net - dsmonrot.git/blob - dsmonrot.ps1
Added logging function (credits to Jason Wasser) and removed unnecessary output
[dsmonrot.git] / dsmonrot.ps1
1 # DSMonRot
2 # Script for rotating Drive Snapshot backups monthly
3 #
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
8 #
9 # Drive Snapshot is copyright by Tom Ehlert
10 # http://www.drivesnapshot.de/
11
12 # Config
13
14 # Path to backup directory
15 [String]$backupDir = "Z:\"
16 # Keep backups for this amount of months (excluding the current month),
17 # -1 for indefinite
18 [Int32]$keepMonths = 2
19 # Rotate BEFORE the beginning of a full backup (default is after a successful
20 # full backup)
21 # WARNING: If this option is set to $True and the full backup fails you could
22 # have NO backup
23 [Boolean]$rotateBeforeBackup = $False
24 # Path to Drive Snapshot
25 [String]$dsPath = "C:\Users\Patrick\Desktop\DSMonRot\snapshot.exe"
26 # Path to Drive Snapshot log file (specify only the file name if you set
27 # $dsLogFileToBackup to $True)
28 #[String]$dsLogFile = "C:\Users\Patrick\Desktop\DSMonRot\snapshot.log"
29 [String]$dsLogFile = "snapshot.log"
30 # Set to $True if you want to put the log file of Drive Snapshot into the same
31 # directory as the backup
32 [Boolean]$dsLogFileToBackup = $True
33 # Disks to backup, see http://www.drivesnapshot.de/en/commandline.htm
34 [String]$disksToBackup = "HD1:1"
35 # Path to directory where DSMonRot stores the log files
36 # Every month a new log file is created
37 [String]$logDir = "C:\Users\Patrick\Desktop\DSMonRot\log"
38 # Keep log files for this amount of months (excluding the current month),
39 # 0 for indefinite (currently not available)
40 [Int32]$keepLogs = 1
41
42 # Map network share to this drive letter, comment out if you don't want to use it
43 [String]$smbDrive = "Z"
44 # Path to network share
45 [String]$smbPath = "\\192.168.0.3\ds"
46 # User and password for connecting to network share, comment out if you don't want to use it
47 # (for example if you want to pass current Windows credentials)
48 [String]$smbUser = "patrick"
49 [String]$smbPassword = ""
50
51 # Send an email if an error occured
52 [Boolean]$emailOnError = $True
53 # From address of email notification
54 [String]$emailFromAddress = "alarm@test.local"
55 # To address of email notification
56 [String]$emailToAddress = "patrick@test.local"
57 # Subject of email notification
58 [String]$emailSubject = "DSMonRot on $env:computername"
59 # Mail server
60 [String]$emailMailserver = "localhost"
61 # SMTP Port
62 [Int32]$emailPort = 25
63 # Use SSL?
64 [Boolean]$emailSSL = $False
65 # Use SMTP Auth?
66 [Boolean]$emailAuth = $False
67 # SMTP Auth User
68 [String]$emailUser = ""
69 # SMTP Auth Password
70 [String]$emailPassword = ""
71
72 # End of config
73
74 # This function is originally by wasserja at https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0
75 <#
76 .Synopsis
77 Write-Log writes a message to a specified log file with the current time stamp.
78 .DESCRIPTION
79 The Write-Log function is designed to add logging capability to other scripts.
80 In addition to writing output and/or verbose you can write to a log file for
81 later debugging.
82 .NOTES
83 Created by: Jason Wasser @wasserja
84 Modified: 11/24/2015 09:30:19 AM
85
86 Changelog:
87 * Code simplification and clarification - thanks to @juneb_get_help
88 * Added documentation.
89 * Renamed LogPath parameter to Path to keep it standard - thanks to @JeffHicks
90 * Revised the Force switch to work as it should - thanks to @JeffHicks
91
92 To Do:
93 * Add error handling if trying to create a log file in a inaccessible location.
94 * Add ability to write $Message to $Verbose or $Error pipelines to eliminate
95 duplicates.
96 .PARAMETER Message
97 Message is the content that you wish to add to the log file.
98 .PARAMETER Path
99 The path to the log file to which you would like to write. By default the function will
100 create the path and file if it does not exist.
101 .PARAMETER Level
102 Specify the criticality of the log information being written to the log (i.e. Error, Warning, Informational)
103 .PARAMETER NoClobber
104 Use NoClobber if you do not wish to overwrite an existing file.
105 .EXAMPLE
106 Write-Log -Message 'Log message'
107 Writes the message to c:\Logs\PowerShellLog.log.
108 .EXAMPLE
109 Write-Log -Message 'Restarting Server.' -Path c:\Logs\Scriptoutput.log
110 Writes the content to the specified log file and creates the path and file specified.
111 .EXAMPLE
112 Write-Log -Message 'Folder does not exist.' -Path c:\Logs\Script.log -Level Error
113 Writes the message to the specified log file as an error message, and writes the message to the error pipeline.
114 .LINK
115 https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0
116 #>
117 function Write-Log
118 {
119 [CmdletBinding()]
120 Param
121 (
122 [Parameter(Mandatory=$true,
123 ValueFromPipelineByPropertyName=$true)]
124 [ValidateNotNullOrEmpty()]
125 [Alias("LogContent")]
126 [string]$Message,
127
128 [Parameter(Mandatory=$false)]
129 [Alias('LogPath')]
130 [string]$Path='C:\Logs\PowerShellLog.log',
131
132 [Parameter(Mandatory=$false)]
133 [ValidateSet("Error","Warn","Info")]
134 [string]$Level="Info",
135
136 [Parameter(Mandatory=$false)]
137 [switch]$NoClobber
138 )
139
140 Begin
141 {
142 # Set VerbosePreference to Continue so that verbose messages are displayed.
143 $VerbosePreference = 'Continue'
144 }
145 Process
146 {
147
148 # If the file already exists and NoClobber was specified, do not write to the log.
149 if ((Test-Path $Path) -AND $NoClobber) {
150 Write-Error "Log file $Path already exists, and you specified NoClobber. Either delete the file or specify a different name."
151 Return
152 }
153
154 # If attempting to write to a log file in a folder/path that doesn't exist create the file including the path.
155 elseif (!(Test-Path $Path)) {
156 Write-Verbose "Creating $Path."
157 $NewLogFile = New-Item $Path -Force -ItemType File
158 }
159
160 else {
161 # Nothing to see here yet.
162 }
163
164 # Format Date for our Log File
165 $FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
166
167 # Write message to error, warning, or verbose pipeline and specify $LevelText
168 switch ($Level) {
169 'Error' {
170 Write-Error $Message
171 $LevelText = 'ERROR:'
172 }
173 'Warn' {
174 Write-Warning $Message
175 $LevelText = 'WARNING:'
176 }
177 'Info' {
178 Write-Verbose $Message
179 $LevelText = 'INFO:'
180 }
181 }
182
183 # Write log entry to $Path
184 "$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append
185 }
186 End
187 {
188 }
189 }
190
191 # Allow SMTP with SSL and SMTP Auth
192 # see: http://petermorrissey.blogspot.de/2013/01/sending-smtp-emails-with-powershell.html
193 function Send-Email([String]$body) {
194 try {
195 $smtp = New-Object System.Net.Mail.SmtpClient($emailMailServer, $emailPort)
196
197 $smtp.EnableSSL = $emailSSL
198
199 if($emailAuth) {
200 $smtp.Credentials = New-Object System.Net.NetworkCredential($emailUser, $emailPassword)
201 }
202
203 $smtp.Send($emailFromAddress, $emailToAddress, $emailSubject, $body)
204 }
205 catch {
206 Write-Log "Could not send email: $_.Exception.Message" -Path $logFile -Level Error
207 }
208 }
209
210 function Rotate-Backup {
211 if($keepMonths -lt 0) {
212 return
213 }
214
215 $keepMonthsCount = $keepMonths
216
217 Get-ChildItem $backupDir -Directory | Where-Object {($_.Name -ne $currMonth) -and ($_.Name -match "^\d{4,}-\d{2}$")} | Sort-Object -Descending |
218 Foreach-Object {
219 if($keepMonthsCount -ge 0) {
220 $keepMonthsCount--
221 }
222
223 if($keepMonthsCount -eq -1) {
224 Write-Log "Deleting backup $($_.FullName)" -Path $logFile -Level Info
225 Remove-Item -Recurse -Force $_.FullName
226 }
227 }
228 }
229
230 $dsAdditionalArgs = @("--UseVSS")
231
232 $errorMessages = @()
233
234 $smbConnected = $False
235 $success = $False
236 $isDiff = $False
237 $dsCommand = ""
238
239 $currMonth = Get-Date -format "yyyy-MM"
240 $currDay = Get-Date -format "yyyy-MM-dd"
241
242
243 if(!(Test-Path $logDir)) {
244 try {
245 New-Item -ItemType directory -Path $logDir -ErrorAction Stop | Out-Null
246 }
247 catch {
248 Write-Error "Could not create log directory $logDir`: $_.Exception.Message"
249 $errorMessages += "Could not create log directory $logDir`: $_.Exception.Message"
250 }
251 }
252
253 $logFile = "$logDir\$currMonth.log"
254
255 if($errorMessages.Count -eq 0) {
256 $startTime = Get-Date -format "yyyy-MM-dd HH:mm:ss"
257 Write-Log "Started at $startTime" -Path $logFile
258
259 if($smbDrive) {
260 Write-Log "Connecting network drive $smbDrive to $smbPath" -Path $logFile
261
262 try {
263 if($smbUser -and $smbPassword) {
264 Write-Log "Connecting to network drive with credentials" -Path $logFile
265
266 $secSmbPassword = $smbPassword | ConvertTo-SecureString -asPlainText -Force
267 $smbCredential = New-Object System.Management.Automation.PSCredential($smbUser, $secSmbPassword)
268
269 New-PSDrive -Name $smbDrive -PSProvider "FileSystem" -Root $smbPath -Credential $smbCredential -Persist -ErrorAction Stop | Out-Null
270 }
271 else {
272 Write-Log "Connecting to network drive without credentials" -Path $logFile
273
274 New-PSDrive -Name $smbDrive -PSProvider "FileSystem" -Root $smbPath -Persist -ErrorAction Stop | Out-Null
275 }
276
277 $smbConnected = $True
278 }
279 catch {
280 Write-Log "Could not connect to network drive $smbDrive`: $_.Exception.Message" -Path $logFile -Level Error
281 $errorMessages += "Could not connect to network drive $smbDrive`: $_.Exception.Message"
282 }
283 }
284
285 if(!(Test-Path $backupDir)) {
286 Write-Log "Directory $backupDir does not exist!" -Path $logFile -Level Error
287 $errorMessages += "Directory $backupDir does not exist!"
288 }
289
290 if($errorMessages.Count -eq 0) {
291 $backupTarget = $backupDir + "\" + $currMonth
292 $backupTargetFull = $backupTarget + "\" + "Full"
293
294 $backupTargetDiff = $backupTarget + "\" + "Diff-" + $currDay
295
296 if((Test-Path $backupTarget) -and (Test-Path $backupTargetFull) -and (Test-Path "$backupTargetFull\*.hsh")) {
297 Write-Log "Doing a differential backup" -Path $logFile
298
299 $isDiff = $True
300
301 if(!(Test-Path $backupTargetDiff)) {
302 try {
303 New-Item -ItemType directory -Path $backupTargetDiff -ErrorAction Stop | Out-Null
304 }
305 catch {
306 Write-Log "Could not create directory $backupTargetDiff`: $_.Exception.Message" -Path $logFile -Level Error
307 $errorMessages += "Could not create directory $backupTargetDiff`: $_.Exception.Message"
308 }
309
310 if($errorMessages.Count -eq 0) {
311 $dsLogPath = if($dsLogFileToBackup) { "$backupTargetDiff\$dsLogFile" } else { $dsLogFile }
312
313 $dsArgs = @($disksToBackup, "--logfile:$dsLogPath", "$backupTargetDiff\`$disk.sna", "-h$backupTargetFull\`$disk.hsh") + $dsAdditionalArgs
314 $dsCommand = "$dsPath $dsArgs"
315
316 Write-Log $dsCommand -Path $logFile
317
318 & $dsPath $dsArgs
319
320 if($LastExitCode -ne 0) {
321 Write-Log "Drive Snapshot failed to backup! Exit code: $LastExitCode" -Path $logFile -Level Error
322 $errorMessages += "Drive Snapshot failed to backup! Exit code: $LastExitCode"
323 }
324 else {
325 Write-Log "Drive Snapshot succeeded!" -Path $logFile
326 $success = $True
327 }
328 }
329 }
330 else {
331 Write-Log "Directory $backupTargetDiff already exists!" -Path $logFile -Level Error
332 $errorMessages += "Directory $backupTargetDiff already exists!"
333 }
334 }
335 else {
336 Write-Log "Doing a full backup" -Path $logFile
337
338 if(!(Test-Path $backupTarget)) {
339 try {
340 New-Item -ItemType directory -Path $backupTarget -ErrorAction Stop | Out-Null
341 }
342 catch {
343 Write-Log "Could not create directory $backupTarget`: $_.Exception.Message" -Path $logFile -Level Error
344 $errorMessages += "Could not create directory $backupTarget`: $_.Exception.Message"
345 }
346 }
347
348 if($errorMessages.Count -eq 0) {
349 if(!(Test-Path $backupTargetFull)) {
350 try {
351 New-Item -ItemType directory -Path $backupTargetFull -ErrorAction Stop | Out-Null
352 }
353 catch {
354 Write-Log "Could not create directory $backupTargetFull`: $_.Exception.Message" -Path $logFile -Level Error
355 $errorMessages += "Could not create directory $backupTargetFull`: $_.Exception.Message"
356 }
357 }
358
359 if($errorMessages.Count -eq 0) {
360 if($rotateBeforeBackup) {
361 Rotate-Backup
362 }
363
364 $dsLogPath = if($dsLogFileToBackup) { "$backupTargetFull\$dsLogFile" } else { $dsLogFile }
365
366 $dsArgs = @($disksToBackup, "--logfile:$dsLogPath", "$backupTargetFull\`$disk.sna") + $dsAdditionalArgs
367 $dsCommand = "$dsPath $dsArgs"
368
369 Write-Log $dsCommand -Path $logFile
370
371 & $dsPath $dsArgs
372
373 if($LastExitCode -ne 0) {
374 Write-Log "Drive Snapshot failed to backup! Exit code: $LastExitCode" -Path $logFile -Level Error
375 $errorMessages += "Drive Snapshot failed to backup! Exit code: $LastExitCode"
376 }
377 else {
378 Write-Log "Drive Snapshot succeeded!" -Path $logFile
379 $success = $True
380 }
381
382 if($rotateBeforeBackup -eq $False -and $success -eq $True) {
383 Rotate-Backup
384 }
385 }
386 }
387 }
388 }
389
390 if($smbConnected) {
391 Write-Log "Disconnecting network drive" -Path $logFile
392
393 try {
394 Remove-PSDrive $smbDrive -ErrorAction Stop
395 }
396 catch {
397 Write-Log "Could not disconnect network drive $smbDrive`: $_.Exception.Message" -Path $logFile -Level Error
398 $errorMessages += "Could not disconnect network drive $smbDrive`: $_.Exception.Message"
399 }
400 }
401 }
402
403 if($emailOnError -and $errorMessages.Count -gt 0) {
404 $emailBody = "This is DSMonRot on $env:computername, started at $startTime.`n"
405 $emailBody += "An error occured while performing a backup. Below are the error messages and some status information.`n`n"
406 $emailBody += "Backup directory: $backupDir`n"
407 $emailBody += "Log directory: $logDir`n"
408 $emailBody += "Current log file: $logFile`n"
409 $emailBody += "Differential backup: $isDiff`n"
410 $emailBody += "Backup successful: $success`n"
411 $emailBody += "Drive Snapshot command: $dsCommand`n`n"
412 $emailBody += ($errorMessages -join "`n")
413
414 Send-Email ($emailBody)
415 }
416
417 $endTime = Get-Date -format "yyyy-MM-dd HH:mm:ss"
418 Write-Log "Ended at $endTime" -Path $logFile

patrick-canterino.de