PowerShell BackupScript Version 2.0 released

That Script was published in 2014, with minimal skills related to Powershell. You, the Community, loved it through.

Via Github, Mail, Facebook, and Twitter, I received a lot of feedback and Bug Reports 🙂

So today, I published a complete new Release, 2.0! With a lot of fixed Bugs, nearly all Functions the old one had and, in my opinion, better Code.

And of course, with a lot of help from the Community.

The Script

########################################################
# Name: BackupScript_v2.ps1                              
# Creator: Michael Seidl aka Techguy                    
# CreationDate: 05.08.2021                              
# LastModified: 05.08.2021                               
# Version: 2.0
# Doc: https://www.techguy.at/tag/backupscript/
# GitHub: https://github.com/Seidlm/PowerShell-Backup-Script
# PSVersion tested: 5
#
# PowerShell Self Service Web Portal at www.au2mator.com/PowerShell
#
#
# Description: Copies the Bakupdirs to the Destination
# You can configure more than one Backupdirs, every Dir
# wil be copied to the Destination.
# Only Change Variables in Variables Section
# Change LoggingLevel to 3 an get more output in Powershell Windows
# 
#
########################################################
#
# www.techguy.at                                        
# www.facebook.com/TechguyAT                            
# www.twitter.com/TechguyAT                             
# michael@techguy.at 
#
#
########################################################






#Variables, only Change here
$Destination = "C:\temp\_Backup" #Copy the Files to this Location

$Versions = "3" #How many of the last Backups you want to keep
$Backupdirs = "C:\Users\seimi\Documents", "C:\Program Files (x86)\Common Files" #What Folders you want to backup
$ExcludeDirs = ($env:SystemDrive + "\Users\.*\AppData\Local"), "C:\Program Files (x86)\Common Files\Adobe" #This list of Directories will not be copied

$logPath = "C:\temp\_Backup"
$LogfileName = "Log" #Log Name
$LoggingLevel = "3" #LoggingLevel only for Output in Powershell Window, 1=smart, 3=Heavy

$Zip = $true #Zip the Backup Destination
$Use7ZIP = $true #7ZIP Module will be installed https://www.powershellgallery.com/packages/7Zip4Powershell/2.0.0
$UseStaging = $true #only if you use ZIP, than we copy file to Staging, zip it and copy the ZIP to destination, like Staging, and to save NetworkBandwith
$StagingPath = "C:\temp\_Staging"

$RemoveBackupDestination = $true #Remove copied files after Zip, only if $Zip is true





#region Functions

function Write-au2matorLog {
    [CmdletBinding()]
    param
    (
        [ValidateSet('DEBUG', 'INFO', 'WARNING', 'ERROR')]
        [string]$Type,
        [string]$Text
    )
       
    # Set logging path
    if (!(Test-Path -Path $logPath)) {
        try {
            $null = New-Item -Path $logPath -ItemType Directory
            Write-Verbose ("Path: ""{0}"" was created." -f $logPath)
        }
        catch {
            Write-Verbose ("Path: ""{0}"" couldn't be created." -f $logPath)
        }
    }
    else {
        Write-Verbose ("Path: ""{0}"" already exists." -f $logPath)
    }
    [string]$logFile = '{0}\{1}_{2}.log' -f $logPath, $(Get-Date -Format 'yyyyMMdd'), $LogfileName
    $logEntry = '{0}: <{1}> <{2}> {3}' -f $(Get-Date -Format dd.MM.yyyy-HH:mm:ss), $Type, $PID, $Text
    
    try { Add-Content -Path $logFile -Value $logEntry }
    catch {
        Start-sleep -Milliseconds 50
        Add-Content -Path $logFile -Value $logEntry
    }
    if ($LoggingLevel -eq "3") { Write-Host $Text }
    
    
}

#endregion Functions

#System Variables, do not change
$PreCheck = $true
$BackUpCheck = $false
$FinalBackupdirs = @()



#SCRIPT
##PRE CHECK
Write-au2matorLog -Type Info -Text "Start the Script"
Write-au2matorLog -Type Info -Text "Create Backup Dirs and Check all Folders an Path if they exist"

try {
    #Create Backup Dir
    $BackupDestination = $Destination + "\Backup-" + (Get-Date -format yyyy-MM-dd) + "-" + (Get-Random -Maximum 100000) + "\"
    New-Item -Path $BackupDestination -ItemType Directory | Out-Null
    Start-sleep -Seconds 5
    Write-au2matorLog -Type Info -Text "Create Backupdir $BackupDestination"

    try {
        #Ceck all Directories
        Write-au2matorLog -Type Info -Text "Check if BackupDirs exist"
        foreach ($Dir in $Backupdirs) {
            if ((Test-Path $Dir)) {
                
                Write-au2matorLog -Type INFO -Text "$Dir is fine"
                $FinalBackupdirs += $Dir
            }
            else {
                Write-au2matorLog -Type WARNING -Text "$Dir does not exist and was removed from Backup"
            }
        }
        try {
            if ($UseStaging) {
                if ((Test-Path $StagingPath)) {
                
                    Write-au2matorLog -Type INFO -Text "$StagingPath is fine"
                }
                else {
                    Write-au2matorLog -Type ERROR -Text "$StagingPath does not exist"
                    Write-au2matorLog -Type ERROR -Text $Error
                    $PreCheck = $false
                }
            }
        }
        catch {
            Write-au2matorLog -Type ERROR -Text "Failed to Check Staging Dir $StagingPath"
            Write-au2matorLog -Type ERROR -Text $Error
            $PreCheck = $false
        }
    }
    catch {
        Write-au2matorLog -Type ERROR -Text "Failed to Check Backupdir $BackupDestination"
        Write-au2matorLog -Type ERROR -Text $Error
        $PreCheck = $false
        
    }
}
catch {
    Write-au2matorLog -Type ERROR -Text "Failed to Create Backupdir $BackupDestination"
    Write-au2matorLog -Type ERROR -Text $Error
    $PreCheck = $false

}




## BACKUP
if ($PreCheck) {
    Write-au2matorLog -Type INFO -Text "PreCheck was good, so start with Backup"

    try {
        Write-au2matorLog -Type INFO -Text "Calculate Size and check Files"
        $BackupDirFiles = @{ } #Hash of BackupDir & Files
        $Files = @()
        $SumMB = 0
        $SumItems = 0
        $SumCount = 0
        $colItems = 0
        $ExcludeString = ""
        foreach ($Entry in $ExcludeDirs) {
            #Exclude the directory itself
            $Temp = "^" + $Entry.Replace("\", "\\").Replace("(", "\(").Replace(")", "\)") + "$"

            #$Temp = $Entry
            $ExcludeString += $Temp + "|"

            #Exclude the directory's children
            $Temp = "^" + $Entry.Replace("\", "\\").Replace("(", "\(").Replace(")", "\)") + "\\.*"

            #$Temp = $Entry
            $ExcludeString += $Temp + "|"
        }
        $ExcludeString = $ExcludeString.Substring(0, $ExcludeString.Length - 1)
        [RegEx]$exclude = $ExcludeString
        
        foreach ($Backup in $FinalBackupdirs) {

            $Files = Get-ChildItem -LiteralPath $Backup -recurse -Attributes D+!ReparsePoint, D+H+!ReparsePoint -ErrorVariable +errItems -ErrorAction SilentlyContinue | 
            ForEach-Object -Process { Add-Member -InputObject $_ -NotePropertyName "ParentFullName" -NotePropertyValue ($_.FullName.Substring(0, $_.FullName.LastIndexOf("\" + $_.Name))) -PassThru -ErrorAction SilentlyContinue } |
            Where-Object { $_.FullName -notmatch $exclude -and $_.ParentFullName -notmatch $exclude } |
            Get-ChildItem -Attributes !D -ErrorVariable +errItems -ErrorAction SilentlyContinue | Where-Object { $_.DirectoryName -notmatch $exclude }
            $BackupDirFiles.Add($Backup, $Files)
    
            $colItems = ($Files | Measure-Object -property length -sum) 
            $Items = 0
            
            $SumMB += $colItems.Sum.ToString()
            $SumItems += $colItems.Count
        }
    
        $TotalMB = "{0:N2}" -f ($SumMB / 1MB) + " MB of Files"
        Write-au2matorLog -Type INFO -Text "There are $SumItems Files with  $TotalMB to copy"
    
        #Log any errors from above from building the list of files to backup.
        [System.Management.Automation.ErrorRecord]$errItem = $null
        foreach ($errItem in $errItems) {
            Write-au2matorLog -Type WARNING -Text ("Skipping `"" + $errItem.TargetObject + "`" Error: " + $errItem.CategoryInfo)
        }
        Remove-Variable errItem
        Remove-Variable errItems

        try {
            Write-au2matorLog -Type INFO -Text "Run Backup"


            foreach ($Backup in $FinalBackupdirs) {
                $Index = $Backup.LastIndexOf("\")
                $SplitBackup = $Backup.substring(0, $Index)
                $Files = $BackupDirFiles[$Backup]
        
                foreach ($File in $Files) {
                    $restpath = $file.fullname.replace($SplitBackup, "")
                    try {
                        # Use New-Item to create the destination directory if it doesn't yet exist. Then copy the file.
                        New-Item -Path (Split-Path -Path $($BackupDestination + $restpath) -Parent) -ItemType "directory" -Force -ErrorAction SilentlyContinue | Out-Null
                        Copy-Item -LiteralPath $file.fullname $($BackupDestination + $restpath) -Force -ErrorAction SilentlyContinue | Out-Null
                        Write-au2matorLog -Type Info -Text $("'" + $File.FullName + "' was copied")
                    }
                    catch {
                        $ErrorCount++
                        Write-au2matorLog -Type Error -Text $("'" + $File.FullName + "' returned an error and was not copied")
                    }
                    $Items += (Get-item -LiteralPath $file.fullname).Length
                    $Index = [array]::IndexOf($BackupDirs, $Backup) + 1
                    $Text = "Copy data Location {0} of {1}" -f $Index , $BackupDirs.Count
                    if ($File.Attributes -ne "Directory") { $count++ }
                }
            }
            $SumCount += $Count
            $SumTotalMB = "{0:N2}" -f ($Items / 1MB) + " MB of Files"
            Write-au2matorLog -Type Info -Text "----------------------"
            Write-au2matorLog -Type Info -Text "Copied $SumCount files with $SumTotalMB"
            if ($ErrorCount ) { Write-au2matorLog -Type Info -Text "$ErrorCount Files could not be copied" }

            $BackUpCheck = $true
        }
        catch {
            
            Write-au2matorLog -Type ERROR -Text "Failed to Backup"
            Write-au2matorLog -Type ERROR -Text $Error
            $BackUpCheck = $false


        }
    }
    catch {
        Write-au2matorLog -Type ERROR -Text "Failed to Measure Backupdir"
        Write-au2matorLog -Type ERROR -Text $Error
        $BackUpCheck = $false
    



    }






}
else {
    Write-au2matorLog -Type ERROR -Text "PreCheck failed so do not run Backup"
    $BackUpCheck = $false
}





## ZIP
if ($BackUpCheck) {
    Write-au2matorLog -Type INFO -Text "BAckUpCheck is fine, so lets se if we need to ZIP"

    
    if ($ZIP) {
        Write-au2matorLog -Type INFO -Text "ZIP is on, so lets go"



        if ($Use7ZIP) {
            Write-au2matorLog -Type INFO -Text "We should use 7Zip for this"
        
            try {
                Write-au2matorLog -Type INFO -Text "Check for the 7ZIP Module"
                if (Get-Module -Name 7Zip4Powershell) {
                    Write-au2matorLog -Type INFO -Text "7ZIP Module is installed"

                }
                else {
                
                    Write-au2matorLog -Type INFO -Text "7ZIP Module is not installed, try to install"
                    Install-Module -Name 7Zip4Powershell -Force
                    Import-Module 7Zip4Powershell

                    


                }

                $Zip = $StagingPath + ("\" + $BackupDestination.Replace($Destination, '').Replace('\', '') + ".zip")
                
                Write-au2matorLog -Type Info -Text "Compress File"
                Compress-7Zip -ArchiveFileName $Zip -Path $BackupDestination
                            
                Write-au2matorLog -Type Info -Text "Move Zip to Destination"
                Move-Item -Path $Zip -Destination $Destination



                $ZIPCheck = $true
            }
            catch {
                Write-au2matorLog -Type ERROR -Text "Error on 7ZIP compression"
                Write-au2matorLog -Type ERROR -Text $Error
                $ZIPCheck = $false
            }


        }
        else {
        
        }
    }
    else {
        Write-au2matorLog -Type INFO -Text "No Zip, so go ahead"
    }


}
else {
    Write-au2matorLog -Type ERROR -Text "BAckUpCheck failed so do not try to ZIP"
}





##CLEANUP BACKUP
if ($Zip -and $RemoveBackupDestination -and $ZIPCheck)
{
    try {
        Write-au2matorLog -Type INFO -Text "Lets remove Backup Dir after ZIP"
        #Remove-Item -Path $BackupDir -Force -Recurse 
        get-childitem -Path $BackupDestination -recurse -Force | remove-item -Confirm:$false -ErrorAction SilentlyContinue -Recurse 
        get-item -Path $BackupDestination | remove-item -Confirm:$false  -ErrorAction SilentlyContinue -Recurse | Out-Null

    }
    catch {
        Write-au2matorLog -Type ERROR -Text "Error to Remove Backup Dir: $BackupDestination"
        Write-au2matorLog -Type ERROR -Text $Error
        
    }
}


##CLEANUP VERSION
Write-au2matorLog -Type Info -Text "Cleanup Backup Dir"

$Count = (Get-ChildItem $Destination | Where-Object { $_.Attributes -eq "Directory" }).count
if ($count -gt $Versions) {
    Write-au2matorLog -Type Info -Text "Found $count Backups"
    $Folder = Get-ChildItem $Destination | Where-Object { $_.Attributes -eq "Directory" } | Sort-Object -Property CreationTime -Descending:$false | Select-Object -First 1

    Write-au2matorLog -Type Info -Text "Remove Dir: $Folder"
    
    $Folder.FullName | Remove-Item -Recurse -Force 
}


$CountZip = (Get-ChildItem $Destination | Where-Object { $_.Attributes -eq "Archive" -and $_.Extension -eq ".zip" }).count
Write-au2matorLog -Type Info -Text "Check if there are more than $Versions Zip in the Backupdir"

if ($CountZip -gt $Versions) {

    $Zip = Get-ChildItem $Destination | Where-Object { $_.Attributes -eq "Archive" -and $_.Extension -eq ".zip" } | Sort-Object -Property CreationTime -Descending:$false | Select-Object -First 1

    Write-au2matorLog -Type Info -Text "Remove Zip: $Zip"
    
    $Zip.FullName | Remove-Item -Recurse -Force 

}

GitHub Repo

Make sure you get the latest version from GitHub.

Seidlm/PowerShell-Backup-Script: PowerShell Backup Script (github.com)

Michael Seidl aka Techguy
au2mate everything

22 thoughts on “PowerShell BackupScript Version 2.0 released”

  1. Hallo,

    ist es möglich, Dateien außerhalb von Unterordner zu (mit)kopieren?

    Vielen Dank 🙂

  2. Hallo

    Vielen Dank für den Script. Ich finde das ganze echt toll. Vor allem mit den möglichen Backup-Versionen.
    Wenn ich nun einen zu speichernden Pfad definiere, dann werden einzig die Unterordner (und die darin enthaltenen Dateien) gespeichert. Jedoch nicht die im Pfad abgespeicherten Dateien.
    Beispiel:

    $BackupDirs = “C:\Temp\OrdnerSave”

    Nun werden die im OrderSave befindlichen UnterOrdner abespeichert. Aber die Files, die direkt im OrdnerSave liegen werden nicht in den $Destination-Pfad kopiert.

  3. Can this script be edited to add encryption and password protection to the resulting zip file?

  4. Hallo,

    tolles Script und genau die Lösung für mich.

    Ich bekomme leider die Fehlermeldung:

    04.01.2022-11:21:38: Failed to Measure Backupdir
    04.01.2022-11:21:38: Es ist nicht möglich, eine Methode für einen Ausdruck aufzurufen, der den NULL hat. Es ist nicht möglich, eine Methode für einen Ausdruck aufzurufen, der den NULL hat. Es ist nicht möglich, eine Methode für einen Ausdruck aufzurufen, der den NULL hat. Es ist nicht möglich, eine Methode für einen Ausdruck aufzurufen, der den NULL hat.
    04.01.2022-11:21:38: BAckUpCheck failed so do not try to ZIP

    Wo liegt denn mein Fehler?

    VG

  5. Hallo Michael,
    vielen Dank für das Script, funktioniert wunderbar und passt genau für meinen Einsatzzweck! Endlich mal etwas, das bei mir auf Anhieb funktioniert 😉
    VG
    Natasha

  6. Hi,

    ich bekomme leider einen Fehler. Eine Idee wo mein Fehler ist? Nutze die neuste Version.

    Failed to Measure Backupdir
    Fehler beim Aufrufen der Methode, da [System.IO.FileInfo] keine Methode mit dem Namen “op_Addition” enthält. Fehler beim Aufrufen der Methode, da [System.IO
    .FileInfo] keine Methode mit dem Namen “op_Addition” enthält.

  7. Servus,
    tolles Skript! Nur stehe ich ein wenig auf der Leitung: Ich möchte die lokalen Files am Rechner sammeln, ein ZIP daraus machen und dieses dann auf einen sicheren Speicherort kopieren (Fileserver). Wo kann ich den endgültigen Zielort (das Share) angeben? Weil wenn ich das Share bei $Destination angebe, werden alle Files auf das Share kopiert (1. Mal kopieren), dann wird von dort, aber lokal am Rechner das ZIP geschnürt (2. Mal kopieren) und dann das fertige ZIP wieder auf die Share kopiert (3. Mal kopieren). Was übersehe ich?

  8. Hallo,

    mit der $Staging Variable kannst du das steuern, denke das ist das was du benötigst

    $UseStaging = $true #only if you use ZIP, than we copy file to Staging, zip it and copy the ZIP to destination, like Staging, and to save NetworkBandwith

  9. Hey Michael,
    Great script! My only thing about it is this. I’m trying to use the staging option, but from the way the script is written, it looks like you back everything up to the remote destination folder and then Zip things up to the Staging directory on the local folder and move that to the remote folder?
    I would like to be able to use the Staging option to backup everything to the local Staging folder and then zip it, move the compressed zip to the remote folder, and then clear the staging folder on the local system?
    Maybe I’m not reading the script right, but that’s how it seems to be working?

    Thanks again for the awesome script!

  10. Hallo,

    wenn ich “$Zip = $true” und “$Use7ZIP = $false” habe,
    wird in $Destination ein Ordner mit Inhalt stellt, aber keine zip-Datei.
    Ich benutze Version 2.1

    Im Skript ab Zeile 269 steht folgendes:
    —————-
    ## ZIP
    if ($BackUpCheck) {
    Write-au2matorLog -Type INFO -Text “BAckUpCheck is fine, so lets se if we need to ZIP”

    if ($ZIP) {
    Write-au2matorLog -Type INFO -Text “ZIP is on, so lets go”

    if ($Use7ZIP) {
    Write-au2matorLog -Type INFO -Text “We should use 7Zip for this”
    try {
    …. hier steht noch mehr
    }
    catch {
    …. hier steht noch mehr
    }
    }
    else {
    ?? -> hier steht nichts drin
    }
    }
    else {
    Write-au2matorLog -Type INFO -Text “No Zip, so go ahead”
    }
    —————-

    Bin ich richtig bei der Annahme, dass da etwas fehlt in diesem Teil, oder habe ich einen Denkfehler ?

    MfG

  11. Hallo Michael,

    geniale Arbeit, danke dafür! Ich komme mit dem exclude leider nicht zurecht, unterordner möchte ich ausschließen, diese greifen aber leider nicht obwohl die Pfade die es nicht mitsichern soll mit vollständigem Speicherpfad angegen sind.

    MfG

  12. Hallo Michael

    Ich habe das BackupScript 2.0 verwendet und wollte fragen, ob die Möglichkeit besteht, mehrere Pfade unter $Destination anzugeben und wie ich das tun müsste?

    Ich habe nämlich das Problem, dass ich gerne ein Backup mit der 3-2-1 Regel machen würde, jedoch funktioniert das Script nur dann, wenn ich einen einzigen Pfad unter $Destination angebe.
    Ich hab schon mehreres ausprobiert, kriege aber immer nur einen Fehler.

  13. Hallo,

    danke für die Script Datei. Funktioniert soweit bis auf:

    In meinem angegebenen Verzeichnis, zum sichern, sind Ordner und im root einige Dateien. Die Dateien werden nicht gesichert. Kann ich das ändern?

    Grüße aus Berlin

  14. Hallo Michael,
    auch von mir herzlichen Dank für dieses geniale Skript, was ich auch schon seit Jahren einsetze.
    In der neuesten Version habe ich jedoch auch das Problem, dass das Staging bei mir nicht funktioniert. Egal welche Parameter ich ändere. Zip = false oder true, Zip und 7zip true und false. Egal was ich einstelle, das Skript beginnt direkt in das Destination Verzeichnis zu sichern. In meinem Fall möchte ich am Standort A die Dateistruktur sichern, zippen und per WebDav zum NAS am Standort B (Destination Pfad) schicken.

    Wie bereits beschrieben, beginnt das Skript aber direkt zum Standort B zu sichern, was mit einer ultraschnellen 16er Leitung sehr langwierig ist.

    Viele Grüße und ein frohes neues Jahr
    Alex

Leave a Comment

Your email address will not be published. Required fields are marked *

*