PSBackup.psm1

Class PSBackupBackupJob {
    [string]$Name
    [string]$Source
    [string]$Destination
    [string]$Parameters
    [string]$LogPath

    PSBackupBackupJob ([string]$Name, [string]$Source, [string]$Destination, [string]$Parameters, [string]$LogPath) {
        $this.Name = $Name
        $this.Source = $Source
        $this.Destination = $Destination
        $this.Parameters = $Parameters
        $this.LogPath = $LogPath
    }

    [string]ToString(){
        return ("{0}|{1}|{2}|{3}|{4}" -f $this.Name, $this.Source, $this.Destination, $this.Parameters, $this.LogPath)
    }
}
Function Import-PSBackupDataFromFile {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [string]$Path
    )
    Write-Verbose "[Import-PSBackupDataFromFile] Path: $Path"

    If (-not (Test-PSBackupFile -Path $Path)) {
        Throw "[Import-PSBackupDataFromFile] BackupJob File Validation Failed"
    } Else {
        Write-Verbose '[Import-PSBackupDataFromFile] BackupJob File Validation Passed'
        $Params = @{
            Path=$Path
            Verbose=$VerbosePreference
        }
        $ImportedData = Import-PowerShellDataFile @Params
        $Output = @()
        ForEach ($Name in $ImportedData.keys) {
            Write-Verbose "[Import-PSBackupDataFromFile] Importing Job: $Name"
            $Obj = New-Object -TypeName PSBackupBackupJob -ArgumentList $Name,
                                                                $($ImportedData[$Name].Source),
                                                                $($ImportedData[$Name].Destination),
                                                                $($ImportedData[$Name].Parameters),
                                                                $($ImportedData[$Name].LogPath)
            $Output += $Obj
        }
        Write-Output $Output
    }
}
Function Get-PSBackup {
    <#
    .EXTERNALHELP PSBackup-help.xml
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$True,ParameterSetName='BackupJobFile')]
        [ValidateScript({Test-Path $_ -PathType Leaf})]
        [ValidateScript({(Get-ChildItem -Path $_).Extension -eq '.psd1'})]
        [ValidateScript({Test-Path $_})]
        [string]$Path
    )

    If ($PSCmdlet.ParameterSetName -eq 'BackupJobFile') {
        Write-Verbose "[Get-PSBackup] From File: $(Resolve-Path -Path $Path)"
        Write-Verbose "[Get-PSBackup] Calling 'Import-PSBackupDataFromFile'"
        $Params = @{
            Path = $Path
            Verbose = $VerbosePreference
        }
        $Output = Import-PSBackupDataFromFile @Params
    }
    Write-Output $Output
}
Function Invoke-PSBackup {
    <#
    .EXTERNALHELP PSBackup-help.xml
    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory=$True,Position=0,ParameterSetName='BackupJobFile')]
        [ValidateScript({Test-Path $_ -PathType Leaf})]
        [ValidateScript({(Get-ChildItem $_).Extension -eq '.psd1'})]
        [ValidateScript({Test-Path $_})]
        [string]$Path
    )

    If ($PSCmdlet.ParameterSetName -eq 'BackupJobFile') {
        Write-Verbose "[Invoke-PSBackup] From File: $(Resolve-Path -Path $Path)"
        $SequenceName = $((Get-ChildItem -Path $Path).BaseName)
        $Params = @{
            Path = $Path
            Verbose = $VerbosePreference
        }
        $BackupJobs = Import-PSBackupDataFromFile @Params
        Write-Verbose "Start: $(Get-Date)"
        Write-Verbose "[Invoke-PSBackup] Backup Sequence: $SequenceName"
        ForEach ($BackupJob in $BackupJobs) {
            Write-Verbose "[Invoke-PSBackup][$SequenceName] Starting Backup Job: $($BackupJob.Name)"
            $LogFileName = ('Backup_{0}_{1:yyyyMMdd}.log' -f $SequenceName, $(Get-Date))
            $LogFile = Join-Path -Path $($BackupJob.LogPath) -ChildPath $LogFileName
            Write-Verbose "[Invoke-PSBackup][$SequenceName][$($BackupJob.Name)] Source: $($BackupJob.Source)"
            Write-Verbose "[Invoke-PSBackup][$SequenceName][$($BackupJob.Name)] Destination: $($BackupJob.Destination)"
            Write-Verbose "[Invoke-PSBackup][$SequenceName][$($BackupJob.Name)] Parameters: $($BackupJob.Parameters)"
            Write-Verbose "[Invoke-PSBackup][$SequenceName][$($BackupJob.Name)] LogFile: $LogFile"
            [string]$ArgumentList = ('"{0}" "{1}" /LOG+:"{2}" {3}' -f $($BackupJob.Source), $($BackupJob.Destination), $LogFile, $($BackupJob.Parameters))
            If ($PSCmdlet.ShouldProcess("Copying '$($BackupJob.Source)' to '$($BackupJob.Destination)'")) {
                Try {
                    $Stopwatch =  [system.diagnostics.stopwatch]::StartNew()
                    Start-Process -FilePath robocopy.exe -ArgumentList $ArgumentList -NoNewWindow -Wait | Out-Null
                    $Stopwatch.Stop()
                } Catch {
                    $ErrorMessage = $_.Exception.Message
                    Write-Warning $ErrorMessage
                }
                $Output = [ordered]@{
                    Name = $($BackupJob.Name)
                    Source = $($BackupJob.Source)
                    Destination = $($BackupJob.Destination)
                    TimeElapsed = $($Stopwatch.Elapsed)
                }
                Write-Output $(New-Object -TypeName PSObject -Property $Output)
            }
        }
    }
}
Function Test-PSBackupFile {
    <#
    .EXTERNALHELP PSBackup-help.xml
    #>

    [CmdletBinding()]
    [OutputType([System.Boolean])]
    Param(
        [Parameter(Mandatory=$True)]
        [string]$Path
    )

    Function script:ValidatePsd1 {
        [CmdletBinding()]
        [OutputType([System.Collections.Hashtable])]
        Param (
            [Parameter(Mandatory = $true)]
            [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformation()]
            [hashtable] $Data
        )
        return $Data
    }

    $TestErrors = 0
    $Params = @{
        Data = $Path
    }
    $PSD1 = ValidatePsd1 @Params

    If (-not $PSD1) {
        $TestErrors++
        Write-Waring "[Test-PSBackupFile][ValidatePsd1] The BackupJob file has failed the PSD1 syntax check."
    } Else {
        Write-Verbose "[Test-PSBackupFile][ValidatePsd1] The BackupJob file syntax is valid."
    }

    $ValidBackupJobNames = @('_ANY')
    ForEach ($Name in $PSD1.keys) {
        ForEach ($BJName in $ValidBackupJobNames) {
            If ($BJName -eq '_ANY') {
                Write-Verbose "[Test-PSBackupFile][BackupJob Names] '$Name' is a valid BackupJob name"
            } ElseIf ($BJName -eq $Name) {
                Write-Verbose "[Test-PSBackupFile][BackupJob Names] '$Name' is a valid BackupJob name"
            } Else {
                $TestErrors++
                Write-Warning "[Test-PSBackupFile][BackupJob Names] '$Name' is not a valid BackupJob name."
            }
        }
    }

    $ValidBackupJobAttributes = @('Source','Destination','Parameters','LogPath')
    ForEach ($ValidAttribute in $ValidBackupJobAttributes) {
        ForEach ($Name in $PSD1.keys) {
            If ($PSD1[$Name].keys -contains $ValidAttribute) {
                Write-Verbose "[Test-PSBackupFile][BackupJobAttributes] '$ValidAttribute' is present in BackupJob '$Name'"
            } Else {
                $TestErrors++
                Write-Warning "[Test-PSBackupFile][BackupJobAttributes] '$ValidAttribute' is not present in BackupJob '$Name'"
            }
        }
    }


    $RequiredBackupJobAttributeCount = 4
    ForEach ($Name in $PSD1.keys) {
        If ($PSD1[$Name].keys.count -lt $RequiredBackupJobAttributeCount) {
            $TestErrors++
            Write-Warning "[Test-PSBackupFile][BackupJobAttributeCount] BackupJob: '$Name' does not have enough attributes. '$RequiredBackupJobAttributeCount' is required."
        } ElseIf ($PSD1[$Name].keys.count -gt $RequiredBackupJobAttributeCount) {
            $TestErrors++
            Write-Warning "[Test-PSBackupFile][BackupJobAttributeCount] BackupJob: '$Name' has too many attributes. '$RequiredBackupJobAttributeCount' is required."
        } ElseIf ($PSD1[$Name].keys.count -eq $RequiredBackupJobAttributeCount) {
            Write-Verbose "[Test-PSBackupFile][BackupJobAttributeCount] BackupJob: '$Name' has a valid number of attributes with '$RequiredBackupJobAttributeCount'."
        }
    }

    ForEach ($Name in $PSD1.keys) {
        ForEach ($Attribute in $PSD1[$Name].keys) {
            If ($PSD1[$Name][$Attribute].keys) {
                $TestErrors++
                Write-Warning "[Test-PSBackupFile][NestingLimit] Cannot exceed Nesting Limits: BackupJob: '$Name' Attribute: '$Attribute' cannot be a hashtable."
            } Else {
                Write-Verbose "[Test-PSBackupFile][NestingLimit] BackupJob: '$Name' Attribute: '$Attribute' is correct."
            }
        }
    }



    If ($TestErrors -ne 0) {
        Write-Warning "[Test-PSBackupFile] BackJob Syntax Errors Found: '$TestErrors'"
        Return $False
    } Else {Return $True}
}