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

patrick-canterino.de