Poor.Backup.System.psm1

#
# Created by: lucas.cueff[at]lucas-cueff.com
#
# v0.1 :
# - manage configuration settings file and environment
# - add or remove files to backup definition
# - log backup
# - compress backup to a zip file
#
#'(c) 2018-2019 lucas-cueff.com - Distributed under Artistic Licence 2.0 (https://opensource.org/licenses/artistic-license-2.0).'

<#
    .SYNOPSIS
    simple powershell module to backup file or folder using Powershell Core
 
    .DESCRIPTION
    simple powershell module to backup file or folder using Powershell Core
     
    .EXAMPLE
    C:\PS> import-module Poor.Backup.System.psm1
#>

Function Start-PoorBackup {
    <#
        .SYNOPSIS
        Start a 'poor' backup task
 
        .DESCRIPTION
        Start a 'poor' backup task
             
        .PARAMETER Facets
        -NoProgressBar switch
        Remove graphical progress bar for copy and compression
         
        .OUTPUTS
        System.IO.DirectoryInfo
        System.IO.FileInfo
 
        .EXAMPLE
        Start a PoorBackup
        C:\PS> Start-PoorBackup
 
        .EXAMPLE
        Start a PoorBackup without progress bar
        C:\PS> Start-PoorBackup -NoProgressBar
    #>

    [cmdletbinding()]
    Param (
        [switch]$NoProgressBar
    )
    process {
        $script:ItemID = $null
        $script:ItemPercent = $null
        if ($NoProgressBar) {
            $tmpProgressPreference = $global:progressPreference
            $global:progressPreference = 'SilentlyContinue'
        }
        if (!($global:PoorBackupSettings.LogDir -and $global:PoorBackupSettings.TargetBackupDir -and $global:PoorBackupSettings.ItemsToBackup)) {
            throw "PoorBackupSettings does not exist. Please use Set-PoorBackupSettings cmdlet to generate it properly or use Import-PoorBackupSettings to load XML settings into memory"
        }
        $ticks = (get-date).Ticks
        $logFileName = join-path $global:PoorBackupSettings.LogDir "PoorBackupSystem_$($ticks).log"
        $BackupFolder = join-path $global:PoorBackupSettings.TargetBackupDir $ticks
        if (!(test-path $BackupFolder)) {
            new-item $BackupFolder -Force -Type Directory | out-null
        }
        Write-PoorBackupLog -Message "==> PoorBackupSystem Starting <==" -Path $logFileName
        Write-PoorBackupLog -Message "Enumarating current back session settings :" -Path $logFileName
        Write-PoorBackupLog -Message "- Target Host Backup directory : $($BackupFolder)" -Path $logFileName
        Write-PoorBackupLog -Message "- Content to backup : $($global:PoorBackupSettings.ItemsToBackup -join ",")" -Path $logFileName
        Write-PoorBackupLog -Message "Starting Real Poor Backup : " -Path $logFileName
        foreach ($item in $global:PoorBackupSettings.ItemsToBackup) {
            $script:ItemID = $script:ItemID + 1
            $script:ItemPercent = $script:ItemID/$global:PoorBackupSettings.ItemsToBackup.count*100 
            if ($script:ItemPercent -lt 100) {
                Write-Progress -Activity "Backuping Poorly Items" -Status "Backing up in Progress:" -CurrentOperation $item -PercentComplete ($script:ItemPercent) -Id 0
            } else {
                Write-Progress -Activity "Backuping Poorly Items" -Status "End of Backup" -CurrentOperation $item -PercentComplete $script:ItemPercent -Id 0 -Completed
            }
            Write-PoorBackupLog -Message "- backup $($item) to $($BackupFolder)..." -Path $logFileName
            if ($item.contains(":")) {
                $targetBackupPath = join-path $BackupFolder ($item.split(":"))[1]
            } else {
                $targetBackupPath = join-path $BackupFolder $item
            }
            Write-PoorBackupLog -Message "full backup target destination folder $($targetBackupPath)" -Path $logFileName
            if (!(test-path $item)) {
                Write-PoorBackupLog -Message "$($item) not found - item will be skipped" -Path $logFileName -Level Warn
            } else {  
                if (!(test-path (split-path $targetBackupPath))) {
                    New-Item (split-path $targetBackupPath) -Force -Type Directory | out-null
                }
                try {
                    Copy-Item $item -Destination (split-path $targetBackupPath) -Recurse -Force | out-null
                } catch {
                    Write-PoorBackupLog -Message "not able to backup $($item) - Error Type: $($_.Exception.GetType().FullName) - Error Message: $($_.Exception.Message)" -Path $logFileName -Level Error
                }
            }
        }
        if ($global:PoorBackupSettings.CompressBackup) {
            try {
                Compress-Archive -Path $BackupFolder -DestinationPath (join-path (split-path $BackupFolder) "$($ticks).zip") -CompressionLevel Optimal -Force | out-null
            } catch {
                Write-PoorBackupLog -Message "not able to archive backup file $((join-path (split-path $BackupFolder) "$($ticks).zip")) - Error Type: $($_.Exception.GetType().FullName) - Error Message: $($_.Exception.Message)" -Path $logFileName -Level Error
            }
            try {
                remove-item $BackupFolder -Force -Recurse | out-null
            } catch {
                Write-PoorBackupLog -Message "not able to remove backup folder $($BackupFolder) after compression - Error Type: $($_.Exception.GetType().FullName) - Error Message: $($_.Exception.Message)" -Path $logFileName -Level Error
            }
        }
        Write-PoorBackupLog -Message "==> PoorBackupSystem Closing <==" -Path $logFileName
        Write-PoorBackupLog -Message "There is no PoorRestoreSystem, DoItYourSelfRestoreSystem ;-)" -Path $logFileName
        if ($NoProgressBar) {
            $global:progressPreference = $tmpProgressPreference
        }
        get-childitem $Global:PoorBackupSettings.TargetBackupDir
    }
}
Function Import-PoorBackupSettings {
    <#
        .SYNOPSIS
        Import PoorBackup Settings file into current PowerShell environment
 
        .DESCRIPTION
        Import PoorBackup Settings file into current PowerShell environment as global variable PoorBackupSettings
         
        .PARAMETER SettingsFile
        -SettingsFile string {full path to target xml file name, directory must exist}
        default value : $home\".PoorBackup\settings.xml"
        full path to custom target xml settings file
         
        .OUTPUTS
        Deserialized.System.Management.Automation.PSCustomObject
         
        .EXAMPLE
        Import PoorBackup settings using default file and path
        C:\PS> Import-PoorBackupSettings
         
        .EXAMPLE
        Import PoorBackup settings using a specific config file
        C:\PS> Import-PoorBackupSettings -SettingsFile C:\tmp\mybackupstuff.xml
    #>

    [cmdletbinding()]
    Param (
      [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$false)]
      [ValidateScript({test-path "$($_)"})]
        [string]$SettingsFile = (join-path $home ".PoorBackup\settings.xml")
    )
    process {
        if (!(test-path $SettingsFile)) {
            throw "$($SettingsFile) does not exist - Please use Set-PoorBackupSettings first to create your configuration file"
        }
        try {
            $global:PoorBackupSettings = Import-Clixml $SettingsFile
        } catch {
            write-error -message "Error Type: $($_.Exception.GetType().FullName)"
            write-error -message "Error Message: $($_.Exception.Message)"
        }
        if (!($global:PoorBackupSettings.LogDir -and $global:PoorBackupSettings.TargetBackupDir -and $global:PoorBackupSettings.ItemsToBackup)) {
            throw "XML file not valid. Please use Set-PoorBackupSettings cmdlet to generate it properly"
        } else {
            $global:PoorBackupSettings
        }
    }
}
Function Set-PoorBackupSettings {
    <#
        .SYNOPSIS
        Set PoorBackup settings
 
        .DESCRIPTION
        Set PoorBackup settings (as global variable and external file) : Log directory, target backup directory, file and folder to backup, backup compression
         
        .PARAMETER LogDir
        -LogDir string {full path to target log directory, directory must exist}
        mandatory
        full path to target log directory
 
        .PARAMETER TargetBackupDir
        -TargetBackupDir string {full path to target backup directory, directory must exist}
        mandatory
        full path to target backup directory
 
        .PARAMETER SettingsFile
        -SettingsFile string {full path to target settings xml file, directory must exist}
        default value : $home\".PoorBackup\settings.xml"
        full path to target settings xml file
 
        .PARAMETER CompressBackup
        -CompressBackup switch
        Enable Backup as a zip file archive
 
        .PARAMETER ItemsToBackup
        -ItemsToBackup array of string
        mandatory
        List of items (file or folder) to backup
 
        .OUTPUTS
        System.IO.DirectoryInfo
        System.IO.FileInfo
         
        .EXAMPLE
        Backup "C:\Users\adm\Documents\GitHub","C:\Users\adm\Documents\Gitlab\","C:\Users\adm\Documents\file.ext" to C:\Backup with compression and log operations to c:\logs
        C:\PS> Set-PoorBackupSettings -LogDir c:\logs -TargetBackupDir C:\Backup -ItemsToBackup @("C:\Users\adm\Documents\GitHub","C:\Users\adm\Documents\Gitlab\","C:\Users\adm\Documents\file.ext") -CompressBackup
    #>

    [cmdletbinding()]
    Param (
      [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$false)]
      [ValidateScript({test-path "$($_)"})]
        [string]$LogDir,
      [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$false)]
      [ValidateScript({test-path "$($_)"})]
        [string]$TargetBackupDir,
      [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$false)]
      [ValidateScript({test-path "$($_)"})]
        [System.Collections.ArrayList]$ItemsToBackup,
      [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$false)]
        [switch]$CompressBackup,
      [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$false)]
        [ValidateScript({test-path (split-path "$($_)")})]
        [string]$SettingsFile = (join-path $home ".PoorBackup\settings.xml")
    )
    process {
        if ($global:PoorBackupSettings) {
            $global:PoorBackupSettings = $null
        }
        if (!(test-path (join-path $home ".PoorBackup"))) {
            New-Item -Path (join-path $home ".PoorBackup") -Force -Type Directory | out-null
        }
        if (!(test-path (join-path $LogDir "PoorBackup"))) {
            New-Item (join-path $LogDir "PoorBackup") -Force -Type Directory | out-null
        }
        $LogDir = (join-path $LogDir "PoorBackup")
        if (!(test-path (join-path $TargetBackupDir "PoorBackup"))) {
            New-Item (join-path $TargetBackupDir "PoorBackup") -Force -Type Directory | out-null
        }
        $TargetBackupDir = (join-path $TargetBackupDir "PoorBackup")
        $global:PoorBackupSettings = New-Object psobject -Property @{
            LogDir = $LogDir
            TargetBackupDir = $TargetBackupDir
            ItemsToBackup = $ItemsToBackup
            CompressBackup = $CompressBackup
            SettingsFile = $SettingsFile
        }
        Export-Clixml -path "$($SettingsFile)" -InputObject $global:PoorBackupSettings -Force
        Get-Item "$($SettingsFile)"
    }
}
Function Add-ItemToPoorBackup {
    <#
        .SYNOPSIS
        Add new item to backup list
 
        .DESCRIPTION
        Add new item (file or folder) to backup list
         
        .PARAMETER BackupItem
        -BackupItem String or System.IO
        mandatory
        new item to add to backup list
 
        .OUTPUTS
        System.String
         
        .EXAMPLE
        Add folder c:\important to backup list
        C:\PS> Add-ItemToPoorBackup -BackupItem "c:\important"
 
        .EXAMPLE
        Add folder c:\important to backup list (pipe mode)
        C:\PS> get-item "c:\important" | Add-ItemToPoorBackup
    #>

    [cmdletbinding()]
    Param (
      [Parameter(Mandatory=$true,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$true)]
      [ValidateScript({($_ -is [System.IO.DirectoryInfo]) -or ($_ -is [System.IO.FileInfo]) -or ($_ -is [System.String])})]
        $BackupItem
    )
    process {
        if (!($global:PoorBackupSettings.LogDir -and $global:PoorBackupSettings.TargetBackupDir -and $global:PoorBackupSettings.ItemsToBackup)) {
            throw "PoorBackupSettings does not exist. Please use Set-PoorBackupSettings cmdlet to generate it properly or use Import-PoorBackupSettings to load XML settings into memory"
        }
        $BackupItemType = $BackupItem | Get-Member | Select-Object -ExpandProperty TypeName -Unique
        switch ($BackupItemType) {
            System.IO.FileInfo {
                if ($BackupItem.fullname) {
                    if ($global:PoorBackupSettings.ItemsToBackup -notcontains $BackupItem.fullname) {
                        $global:PoorBackupSettings.ItemsToBackup.add($BackupItem.fullname)
                    }
                }
            }
            System.IO.DirectoryInfo {
                if ($BackupItem.fullname) {
                    if ($global:PoorBackupSettings.ItemsToBackup -notcontains $BackupItem.fullname) {
                        $global:PoorBackupSettings.ItemsToBackup.add($BackupItem.fullname)
                    }
                }
            }
            System.String {
                if ($global:PoorBackupSettings.ItemsToBackup -notcontains $BackupItem)  {
                    $global:PoorBackupSettings.ItemsToBackup.add($BackupItem)
                }
            }
        }
        $global:PoorBackupSettings.ItemsToBackup
    }
}
Function Remove-ItemFromPoorBackup {
    <#
        .SYNOPSIS
        Remove new item from backup list
 
        .DESCRIPTION
        Remove new item (file or folder) from backup list
         
        .PARAMETER BackupItem
        -BackupItem String or System.IO
        mandatory
        current item to remove from backup list
 
        .OUTPUTS
        System.String
         
        .EXAMPLE
        Remove folder c:\important from backup list
        C:\PS> Remove-ItemFromPoorBackup -BackupItem "c:\important"
 
        .EXAMPLE
        Remove folder c:\important from backup list (pipe mode)
        C:\PS> get-item "c:\important" | Remove-ItemFromPoorBackup
    #>

    [cmdletbinding()]
    Param (
      [Parameter(Mandatory=$true,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$true)]
      [ValidateScript({($_ -is [System.IO.DirectoryInfo]) -or ($_ -is [System.IO.FileInfo]) -or ($_ -is [System.String])})]
        $BackupItem
    )
    Process {
        if (!($global:PoorBackupSettings.LogDir -and $global:PoorBackupSettings.TargetBackupDir -and $global:PoorBackupSettings.ItemsToBackup)) {
            throw "PoorBackupSettings does not exist. Please use Set-PoorBackupSettings cmdlet to generate it properly or use Import-PoorBackupSettings to load XML settings into memory"
        }
        $BackupItemType = $BackupItem | Get-Member | Select-Object -ExpandProperty TypeName -Unique
        switch ($BackupItemType) {
            System.IO.FileInfo {
                if ($BackupItem.fullname) {
                    if ($global:PoorBackupSettings.ItemsToBackup -contains $BackupItem.fullname) {
                        $global:PoorBackupSettings.ItemsToBackup.remove($BackupItem.fullname)
                    }
                }
            }
            System.IO.DirectoryInfo {
                if ($BackupItem.fullname) {
                    if ($global:PoorBackupSettings.ItemsToBackup -contains $BackupItem.fullname) {
                        $global:PoorBackupSettings.ItemsToBackup.remove($BackupItem.fullname)
                    }
                }
            }
            System.String {
                if ($global:PoorBackupSettings.ItemsToBackup -contains $BackupItem)  {
                    $global:PoorBackupSettings.ItemsToBackup.remove($BackupItem)
                }
            }
        }
        $global:PoorBackupSettings.ItemsToBackup
    }
}
function Write-PoorBackupLog {
    <#
        .SYNOPSIS
        Write to log file
 
        .DESCRIPTION
        Write to log file
         
        .PARAMETER Message
        -Message string
        mandatory
        string to write to log file
 
        .PARAMETER Path
        -Path string {full path to target log file}
        mandatory
        full path to log file to write in
 
        .PARAMETER Level
        -Level string {"Error","Warn","Info"}
        default value : Info
        Type of information to log
         
        .OUTPUTS
        none
         
        .EXAMPLE
        Write "Hello world" as information to "c:\logs\test.log"
        C:\PS> Write-PoorBackupLog -Message "Hello world" -Path "c:\logs\test.log"
         
        .EXAMPLE
        Write "Bad world" as error to "c:\logs\test.log"
        C:\PS> Write-PoorBackupLog -Message "Bad world" -Path "c:\logs\test.log" -Level Error
    #>
 
    [CmdletBinding()] 
    Param ( 
        [Parameter(Mandatory=$true, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$true)] 
        [ValidateNotNullOrEmpty()]
            [string]$Message, 
        [Parameter(Mandatory=$true)] 
            [string]$Path, 
        [Parameter(Mandatory=$false)] 
        [ValidateSet("Error","Warn","Info")] 
            [string]$Level="Info"
    ) 
    Process { 
        if (!(Test-Path $Path)) {
            Write-Verbose "Creating $($Path)." 
            New-Item $Path -Force -Type File | out-null
        } 
        $FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" 
        try {
            "$($FormattedDate) $($Level.ToUpper()): $($Message)" | Out-File -FilePath $Path -Append
            Write-Debug "$($FormattedDate) $($Level.ToUpper()): $($Message)"
        } catch {
            write-error -message "Error Type: $($_.Exception.GetType().FullName)"
            write-error -message "Error Message: $($_.Exception.Message)"
        }
    } 
}

Export-ModuleMember Start-PoorBackup, Set-PoorBackupSettings, Add-ItemToPoorBackup, Remove-ItemFromPoorBackup, Import-PoorBackupSettings, Write-PoorBackupLog