CheckBackups.psm1

<#
 
.SYNOPSIS
    Checks if backups are not missing, their sizes is growing correctly and if there are any issues,
    then it is sending notification by email.
 
.PARAMETER PSScriptRoot
    Default working location for script, containing .ini file etc. If not specified, it is taken as actual PSScriptRoot localisation.
 
.PARAMETER ParameterSplitSymbol
    Symbol that is used as a splitter in .ini file to split arguments.
 
#>

function Check-Backups {
    [CmdletBinding(DefaultParameterSetName='Default')]
    Param(    
        [String]$PSScriptRoot = $PSScriptRoot,
        [String]$IniFileName = "CheckBackups.ini",                
        [String]$IniFileLocation = (Join-Path $PSScriptRoot $IniFileName),
        [String]$LogFileName = "CheckBackups.log",
        [String]$LogFilePath = (Join-Path $PSScriptRoot $LogFileName),
        [String]$ParameterSplitSymbol = "&",
        
        [Parameter(ParameterSetName='SendEmail')]
        [Switch]$SendEmailNotifications = $False,
        [Parameter(ParameterSetName='SendEmail', Mandatory=$True)]
        [String]$emailFrom,
        [Parameter(ParameterSetName='SendEmail', Mandatory=$True)]        
        [String]$emailTo,
        [Parameter(ParameterSetName='SendEmail', Mandatory=$True)]        
        [String]$emailPassword,
        [Parameter(ParameterSetName='SendEmail')]        
        [String]$emailSubject = "Check-Backups Powershell found a problem.",
        [Parameter(ParameterSetName='SendEmail')]                
        [String]$emailSmtp = "smtp.gmail.com",
        [Parameter(ParameterSetName='SendEmail')]                
        [String]$emailPortsmtp = "587"
        )

    BEGIN {
        Write-Verbose "Starting BEGIN block of Check-Backup function."    

        function Print-Parameters([String]$parameters) {
            Write-Verbose ("Printing parameters for line: " + $parameters)
            $counter = 1
            $line = ""
            $splittedParameters = $parameters.Split("{" + $ParameterSplitSymbol + "}")
            foreach ($parameter in $splittedParameters) {
                $line += " $counter" + ": $parameter"
                $counter++
            }
            Write-Verbose ("Printing parameters returning final line: " + $line)
            return $line
        }

        function Get-Size([String]$path) {
            ((Get-ChildItem -path $path -recurse | Measure-Object -property length -sum).sum / 1kB)
        }
        
    }

    PROCESS {
        Write-Verbose "Starting PROCESS block of Check-Backup function."    

        if (Test-Path $IniFileLocation) {
            Write-Verbose ("Reading input from ini file in path: " + $IniFileLocation)
            $iniFile = [System.IO.File]::OpenText($IniFileLocation)
            $iniFile = Get-Content $IniFileLocation

            $logger = New-Object SimpleLogger($LogFileName, $LogFilePath)
            [EmailAutoresponder]$emailAutoresponder = New-Object EmailAutoresponder($emailFrom, $emailTo, $emailPassword, $emailSubject, $emailSmtp, $emailPortsmtp)

            foreach ($line in $iniFile) {
                if ($line[0] -eq "#" -or $line.Length -eq 0) {
                    Write-Verbose ("Skipping empty or comment line: " + $line + " .")
                    continue
                }
                
                Write-Verbose ("Executing line with parameters:" + (Print-Parameters $line))
                $splittedLine = $line.Split("{" + $ParameterSplitSymbol + "}")
                $backupPath = $splittedLine[0]
                $backupType = $splittedLine[1].ToInt32()
                $sendEmailNotification = ($splittedLine[2] -like "true")
                $expectedColdMinSizeChange = [Int]$splittedLine[3]
                $expectedColdMaxSizeChange = [Int]$splittedLine[4]                

                $emailAutoresponder.sendMail = $sendEmailNotification                

                [Backup]$backup = New-Object Backup($backupPath, $expectedColdMinSizeChange, $expectedColdMaxSizeChange, $logger, $emailAutoresponder)
                
                switch ($backupType) {
                    1 {
                        $backup.ValidateWeeklyBackups()
                    }
                    Default {
                        $log = "Undefined backup type choosen. Check ini file in a column backup type"
                        $logger.AddLog($log)
                        $emailAutoresponder.AppendToBody($log)
                    }
                }
            }
        }
        else {
            Write-Error ("Ini file not found, cannot proceed program execution. Provided path: " + $IniFileLocation)
            return 1
        }

        if ($emailAutoresponder -ne $null -and $SendEmailNotifications -eq $True) {
            Write-Verbose "Trying to send email notification."
            if ($emailAutoresponder.SendEmail() -eq $True) {
                $logger.AddLog("Successfuly sent email notification to: " + $emailTo)
            }
        }
    }

    END {}
    
}

Class Backup {
    [String]$backupPath
    [Int]$expectedMinColdSizeChange
    [Int]$expectedMaxColdSizeChange        
    [String[]]$daysOfTheWeek = @("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday")
    [SimpleLogger]$logger
    [EmailAutoresponder]$emailAutoresponder
    
    Backup($backupPath, $expectedMinColdSizeChange, $expectedMaxColdSizeChange, [ref]$logger, [ref]$emailAutoresponder) {
        $this.backupPath = $backupPath
        $this.expectedMinColdSizeChange = $expectedMinColdSizeChange
        $this.expectedMaxColdSizeChange = $expectedMaxColdSizeChange        
        $this.logger = $logger.Value
        $this.emailAutoresponder = $emailAutoresponder.Value
    }

    ValidateWeeklyBackups() {
        Write-Verbose ("Listing directories in backup path: " + $this.backupPath)
        if (!(Test-Path $this.backupPath)) {
            $this.AddLogAndAppendToBody("Provided backup path doesn't exists: " + $this.backupPath)
            return
        }

        $foldersListInPath = Get-ChildItem -Directory -Name -Path $this.backupPath
        $coldBackups = @()
        Write-Verbose ("Looking for cold backups in backup path.")
        foreach ($folderName in $foldersListInPath) {
            if ($folderName -match "^[\d\.]+$" -and $folderName.Length -eq 8) {
                $coldBackups += @([Int]$folderName)
            }
        }
        Write-Verbose ("Cold backups found: " + $coldBackups)

        [Array]::Sort($coldBackups)
        $this.logger.AddLog("Found " + $coldBackups.Count + " cold backups in path: " + $this.backupPath)

        $firstPath = $True
        $previousSize = 0
        foreach ($path in $coldBackups) {
            if ($firstPath -eq $True) {
                $previousSize = Get-Size (Join-Path $this.backupPath $path)
                $firstPath = $False
                continue
            }
            $fullPath = Join-Path $this.backupPath $path
            $backupSize = (Get-Size $fullPath)
            $sizeDiff = $backupSize - $previousSize

            $this.CheckBackupSize($backupSize, $sizeDiff, $this.expectedColdSizeChange, $fullPath)
        }

        foreach ($day in $this.daysOfTheWeek) {
            if (!(Test-Path (Join-Path $this.backupPath $day))) {
                $this.AddLogAndAppendToBody("Missing " + $day + " backup in a path: " + $this.backupPath)
                continue
            }
            $backupSize = Get-Size (Join-Path $this.backupPath $day)
            if ($backupSize -eq 0) {
                $this.AddLogAndAppendToBody("Backup in path " + (Join-Path $this.backupPath $day) + " is empty.")
            }
        }
    }

    hidden CheckBackupSize($backupSize, $sizeDiff, $fullPath) {
        if ($backupSize -eq 0) {
            $this.AddLogAndAppendToBody("Size of a " + $fullPath + " is 0. Probably missing backup.")
        }
        elseif ($sizeDiff -ge $this.expectedMinColdSizeChange -and $sizeDiff -le $this.expectedMaxColdSizeChange) {
            return
        }
        else {
            $this.AddLogAndAppendToBody("Size difference of a " + $fullPath + " is not inside expected range: <" + $this.expectedMinColdSizeChange + "; " + $this.expectedMaxColdSizeChange + ">")            
        }
    }

    hidden AddLogAndAppendToBody($log) {
        $this.logger.AddLog($log)
        $this.emailAutoresponder.AppendToBody($log)
    }
}

Export-ModuleMember -Function Check-Backups