PowerShell BackupScript Version 1.4 released

I have updated my PowerShell Backup Script and want to provide you the new Release

I have added some New Feature, Staging Folder, Exclude Dirs, 7Zip Support and fixed some Bugs.

Please comment and rate the Script on TechNet

IMPORTANT, the Script is now on GitHub: https://github.com/Seidlm/PowerShell-Backup-Script

The Script

########################################################
# Name: BackupScript.ps1                              
# Creator: Michael Seidl aka Techguy                    
# CreationDate: 21.01.2014                              
# LastModified: 19.02.2019                               
# Version: 1.4
# Doc: https://www.techguy.at/tag/backupscript/
# PSVersion tested: 3 and 4
#
# Description: Copies the Bakupdirs to the Destination
# You can configure more than one Backupdirs, every Dir
# wil be copied to the Destination. A Progress Bar
# is showing the Status of copied MB to the total MB
# Only Change Variables in Variables Section
# Change LoggingLevel to 3 an get more output in Powershell Windows
# 
#
# Beschreibung: Kopiert die BackupDirs in den Destination
# Ordner. Es können mehr als nur ein Ordner angegeben
# werden. Der Prozess wid mit einem Statusbar angezeigt
# diese zeigt die kopieren MB im Vergleich zu den gesamten
# MB an.
# Nur die Werte unter Variables ändern
# Ändere den Logginglevel zu 3 und erhalte die gesamte Ausgabe im PS Fenster
# Version 1.4
#               NEW: 7ZIP Support
#               FIX: Ordering at old Backup deletion
#               FIX: Exclude Dir is now working
#               NEW: Staging folder for ZIP
# Version 1.3 - NEW: Send Mail Function
#               NEW: Backup Destination will be zipped
#               NEW: Exclude Dir
#               FIX: Logging Level
#               FIX: Delete old Folder by CreationTime
#
# Version 1.2 - FIX: Delete last Backup dirs, changed to support older PS Version
#               FIX: Fixed the Count in the Statusbar
#               FIX: Fixed Location Count in Statusbar
#
# Version 1.1 - CHANGE: Enhanced the Logging to a Textfile and write output, copy Log file to Backupdir
#             - FIX: Renamed some Variables an have done some cosmetic changes
#             - CHANGE: Define the Log Name in Variables
#
# Version 1.0 - RTM
########################################################
#
# www.techguy.at                                        
# www.facebook.com/TechguyAT                            
# www.twitter.com/TechguyAT                             
# michael@techguy.at 
########################################################

#Variables, only Change here
$Destination="\\SVATHOME002\Backup$\NB BaseIT" #Copy the Files to this Location
$Destination="C:\Users\seimi\Downloads"
$Staging="C:\Users\seimi\Downloads\Staging"
$ClearStaging=$true # When $true, Staging Dir will be cleared
$Versions="5" #How many of the last Backups you want to keep
$BackupDirs="C:\Users\seimi\OneDrive - Seidl Michael\0-Temp" #What Folders you want to backup

$ExcludeDirs="C:\Users\seimi\OneDrive - Seidl Michael\0-Temp\Dir1","C:\Users\seimi\OneDrive - Seidl Michael\0-Temp\Dir2" #This list of Directories will not be copied

$LogName="Log.txt" #Log Name
$LoggingLevel="3" #LoggingLevel only for Output in Powershell Window, 1=smart, 3=Heavy
$Zip=$true #Zip the Backup Destination
$Use7ZIP=$true #Make sure it is installed
$RemoveBackupDestination=$false #Remove copied files after Zip, only if $Zip is true
$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



#Send Mail Settings
$SendEmail = $false                    # = $true if you want to enable send report to e-mail (SMTP send)
$EmailTo   = 'test@domain.com'              #user@domain.something (for multiple users use "User01 <user01@example.com>" ,"User02 <user02@example.com>" )
$EmailFrom = 'from@domain.com'   #matthew@domain 
$EmailSMTP = 'smtp.domain.com' #smtp server adress, DNS hostname.


#STOP-no changes from here
#STOP-no changes from here
#Settings - do not change anything from here

$ExcludeString=""
#[string[]]$excludedArray = $ExcludeDirs -split "," 
foreach ($Entry in $ExcludeDirs)
{
    $Temp="^"+$Entry.Replace("\","\\")
    $ExcludeString+=$Temp+"|"
}
$ExcludeString=$ExcludeString.Substring(0,$ExcludeString.Length-1)
#$ExcludeString
[RegEx]$exclude = $ExcludeString

if ($UseStaging -and $Zip)
{
    #Logging "INFO" "Use Temp Backup Dir"
    $Backupdir=$Staging +"\Backup-"+ (Get-Date -format yyyy-MM-dd)+"-"+(Get-Random -Maximum 100000)+"\"
}
else
{
    #Logging "INFO" "Use orig Backup Dir"
    $Backupdir=$Destination +"\Backup-"+ (Get-Date -format yyyy-MM-dd)+"-"+(Get-Random -Maximum 100000)+"\"
}



#$BackupdirTemp=$Temp +"\Backup-"+ (Get-Date -format yyyy-MM-dd)+"-"+(Get-Random -Maximum 100000)+"\"
$Log=$Backupdir+$LogName
$Log
$Items=0
$Count=0
$ErrorCount=0
$StartDate=Get-Date #-format dd.MM.yyyy-HH:mm:ss

#FUNCTION
#Logging
Function Logging ($State, $Message) {
    $Datum=Get-Date -format dd.MM.yyyy-HH:mm:ss

    if (!(Test-Path -Path $Log)) {
        New-Item -Path $Log -ItemType File | Out-Null
    }
    $Text="$Datum - $State"+":"+" $Message"

    if ($LoggingLevel -eq "1" -and $Message -notmatch "was copied") {Write-Host $Text}
    elseif ($LoggingLevel -eq "3") {Write-Host $Text}
   
    add-Content -Path $Log -Value $Text
    
}


#Create Backupdir
Function Create-Backupdir {
    New-Item -Path $Backupdir -ItemType Directory | Out-Null
    sleep -Seconds 5
    Logging "INFO" "Create Backupdir $Backupdir"
}

#Delete Backupdir
Function Delete-Backupdir {
    $Folder=Get-ChildItem $Destination | where {$_.Attributes -eq "Directory"} | Sort-Object -Property CreationTime -Descending:$false | Select-Object -First 1

    Logging "INFO" "Remove Dir: $Folder"
    
    $Folder.FullName | Remove-Item -Recurse -Force 
}


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

    Logging "INFO" "Remove Zip: $Zip"
    
    $Zip.FullName | Remove-Item -Recurse -Force 
}

#Check if Backupdirs and Destination is available
function Check-Dir {
    Logging "INFO" "Check if BackupDir and Destination exists"
    if (!(Test-Path $BackupDirs)) {
        return $false
        Logging "Error" "$BackupDirs does not exist"
    }
    if (!(Test-Path $Destination)) {
        return $false
        Logging "Error" "$Destination does not exist"
    }
}

#Save all the Files
Function Make-Backup {
    Logging "INFO" "Started the Backup"
    $Files=@()
    $SumMB=0
    $SumItems=0
    $SumCount=0
    $colItems=0
    Logging "INFO" "Count all files and create the Top Level Directories"

    foreach ($Backup in $BackupDirs) {
        $colItems = (Get-ChildItem $Backup -recurse | Where-Object {$_.mode -notmatch "h"} | Measure-Object -property length -sum) 
        $Items=0
        $FilesCount += Get-ChildItem $Backup -Recurse | Where-Object {$_.mode -notmatch "h"}  
        Copy-Item -Path $Backup -Destination $Backupdir -Force -ErrorAction SilentlyContinue
        $SumMB+=$colItems.Sum.ToString()
        $SumItems+=$colItems.Count
    }

    $TotalMB="{0:N2}" -f ($SumMB / 1MB) + " MB of Files"
    Logging "INFO" "There are $SumItems Files with  $TotalMB to copy"

    foreach ($Backup in $BackupDirs) {
        $Index=$Backup.LastIndexOf("\")
        $SplitBackup=$Backup.substring(0,$Index)
        $Files = Get-ChildItem $Backup -Recurse  | select * | Where-Object {$_.mode -notmatch "h" -and $_.fullname -notmatch $exclude} | select fullname #$_.mode -notmatch "h" -and 

        foreach ($File in $Files) {
            $restpath = $file.fullname.replace($SplitBackup,"")
            try {
                Copy-Item  $file.fullname $($Backupdir+$restpath) -Force -ErrorAction SilentlyContinue |Out-Null
                Logging "INFO" "$file was copied"
            }
            catch {
                $ErrorCount++
                Logging "ERROR" "$file returned an error an was not copied"
            }
            $Items += (Get-item $file.fullname).Length
            $status = "Copy file {0} of {1} and copied {3} MB of {4} MB: {2}" -f $count,$SumItems,$file.Name,("{0:N2}" -f ($Items / 1MB)).ToString(),("{0:N2}" -f ($SumMB / 1MB)).ToString()
            $Index=[array]::IndexOf($BackupDirs,$Backup)+1
            $Text="Copy data Location {0} of {1}" -f $Index ,$BackupDirs.Count
            Write-Progress -Activity $Text $status -PercentComplete ($Items / $SumMB*100)  
            if ($File.Attributes -ne "Directory") {$count++}
        }
    }
    $SumCount+=$Count
    $SumTotalMB="{0:N2}" -f ($Items / 1MB) + " MB of Files"
    Logging "INFO" "----------------------"
    Logging "INFO" "Copied $SumCount files with $SumTotalMB"
    Logging "INFO" "$ErrorCount Files could not be copied"


    # Send e-mail with reports as attachments
    if ($SendEmail -eq $true) {
        $EmailSubject = "Backup Email $(get-date -format MM.yyyy)"
        $EmailBody = "Backup Script $(get-date -format MM.yyyy) (last Month).`nYours sincerely `Matthew - SYSTEM ADMINISTRATOR"
        Logging "INFO" "Sending e-mail to $EmailTo from $EmailFrom (SMTPServer = $EmailSMTP) "
        ### the attachment is $log 
        Send-MailMessage -To $EmailTo -From $EmailFrom -Subject $EmailSubject -Body $EmailBody -SmtpServer $EmailSMTP -attachment $Log 
    }
}


#create Backup Dir



Create-Backupdir
Logging "INFO" "----------------------"
Logging "INFO" "Start the Script"

#Check if Backupdir needs to be cleaned and create Backupdir
$Count=(Get-ChildItem $Destination | where {$_.Attributes -eq "Directory"}).count
Logging "INFO" "Check if there are more than $Versions Directories in the Backupdir"

if ($count -gt $Versions) 
{

    Delete-Backupdir
}


$CountZip=(Get-ChildItem $Destination | where {$_.Attributes -eq "Archive" -and $_.Extension -eq ".zip"}).count
Logging "INFO" "Check if there are more than $Versions Zip in the Backupdir"

if ($CountZip -gt $Versions) {

    Delete-Zip 

}

#Check if all Dir are existing and do the Backup
$CheckDir=Check-Dir

if ($CheckDir -eq $false) {
    Logging "ERROR" "One of the Directory are not available, Script has stopped"
} else {
    Make-Backup

    $Enddate=Get-Date #-format dd.MM.yyyy-HH:mm:ss
    $span = $EndDate - $StartDate
    $Minutes=$span.Minutes
    $Seconds=$Span.Seconds

    Logging "INFO" "Backupduration $Minutes Minutes and $Seconds Seconds"
    Logging "INFO" "----------------------"
    Logging "INFO" "----------------------" 

    if ($Zip)
    {
        Logging "INFO" "Compress the Backup Destination"
        
        if ($Use7ZIP)
        {
            Logging "INFO" "Use 7ZIP"
            if (-not (test-path "$env:ProgramFiles\7-Zip\7z.exe")) {Logging "WARNING" "7Zip not found"} 
            set-alias sz "$env:ProgramFiles\7-Zip\7z.exe" 
            #sz a -t7z "$directory\$zipfile" "$directory\$name"    
                    
            if ($UseStaging -and $Zip)
            {
                $Zip=$Staging+("\"+$Backupdir.Replace($Staging,'').Replace('\','')+".zip")
                sz a -t7z $Zip $Backupdir
                
                Logging "INFO" "Move Zip to Destination"
                Move-Item -Path $Zip -Destination $Destination

                if ($ClearStaging)
                {
                Logging "INFO" "Clear Staging"
                Get-ChildItem -Path $Staging -Recurse -Force | remove-item -Confirm:$false -Recurse
                }

            }
            else
            {
                sz a -t7z ($Destination+("\"+$Backupdir.Replace($Destination,'').Replace('\','')+".zip")) $Backupdir
            }
                
        }
        else
        {
        Logging "INFO" "Use Powershell Compress-Archive"
        Compress-Archive -Path $Backupdir -DestinationPath ($Destination+("\"+$Backupdir.Replace($Destination,'').Replace('\','')+".zip")) -CompressionLevel Optimal -Force

        }






        If ($RemoveBackupDestination)
        {
            Logging "INFO" "Backupduration $Minutes Minutes and $Seconds Seconds"

            #Remove-Item -Path $BackupDir -Force -Recurse 
            get-childitem -Path $BackupDir -recurse -Force  | remove-item -Confirm:$false -Recurse
            get-item -Path $BackupDir   | remove-item -Confirm:$false -Recurse
        }
    }
}

Write-Host "Press any key to close ..."

$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")


Download is available at TechNet Gallery: http://gallery.technet.microsoft.com/PowerShell-Backup-Script-956f312c

All my TechNet Downloads: 1jrYQoA

PLEASE RATE AND COMMENT.

Michael Seidl aka Techguy

25 thoughts on “PowerShell BackupScript Version 1.4 released”

  1. is there a way to exclude file types? If this is discussed elsewhere please point me to that blog.

  2. You have an additional “+” BEFORE the “=” on line 91. Here:

    $ExcludeString+=$Temp+”|”

    It makes the script run but actually fail to do anything useful. When I removed this + everything ran as expected.

    All the best.

  3. Hi,
    I’m new in this Powershell stuff. I adapt your code so I can do the backup of our server but the only information that I can see is the log file in the staging folder and in the Destination. I’m not able to retried any information from the files that I want to save. In the destination folder I just can see the log and the backup zip and in this one it’s empty.

  4. Per Ljungstrom

    Thank’s for a great tool. Not sure I missed something but I do not see the tool is backing up hidden files like user Appdata folder (using 1.3 now but will update)

  5. Per Ljungstrom

    OK, 1.4 seems to be stable for this, I do not see the problem there. I other words – other folders was empty periodically as well in 1.3, not only hidden ones. In 1.4 I have not seen it yet.
    Thanks

  6. Firstly, thank you for a great script.

    Is there a way when backing up multiple locations to put them in their own folder inside the destination?

    For example:
    Backup c:\users\bill\documents
    Backup c:\users\john\documents

    currently it would place all documents in the s:\backups\ and mix both users documents

    Would like it to take the parent folder name (up 1 level) and use that as the destination. for example:

    S:\backups\bill\documents
    S:\backups\john\documents

    This way Bill and John are separate folders and can find files easier.

  7. I like your script, you have done a great Job.
    After monthes of waiting for AVM supporting SMB V2/3 now i have tested making backup using “backupscript V1.4”.
    I use Windows 10/10Pro V1909 backing up to FRITZ.NAS.
    A first impression it seems to work well, further testing has to be done.
    But there are some issues:
    Progress-bar:
    – counting files works not exactly (i.e. Copied 519 of 481 files with…)
    – counting dataspace works not exactly (i.e. …with 2.176,43 MB of 1.169,58 MB of Files)
    Log-file:
    – some files are marked as “ERROR: returned an error an was not copied” but are correctly copied to media
    – Showing backupduration INFO: Backupduration 0 Hours 6 Minutes and 20 Seconds
    “24.11.2019-15:47:03 – INFO: There are 111152 Files with 303.772,75 MB of Files to copy
    25.11.2019-01:39:37 – INFO: Backupduration 53 Minutes and 10 Seconds”

    best regards

  8. Thank you for this. Its great, I am however trying to add some code to make a change.

    Need to addin where script will check the \Users folder for all listed users.
    Then prompt you to choose which user Dir to Backup.
    Upon choosing, then auto fill in the user for ‘seimi”

    Is that possible?

  9. Great script
    Could you replace 7zip with powershell Compress-Archive to avoid installing 7zip
    Would be greatly appreciated

  10. Andy Stallard

    Hi Michael,
    Your script is very good and I have learned some PS in the process. The only thing I can’t work out is how I can back up a folder and it’s content to a less complex path.

    e.g.
    C:\Data\B\B1\B11\B111 ***I just want this set of folders and subs***

    I would like to be able to save the files to E:\Backups\B_yyyymmdd with the same sub folders but what I get is;
    E:\Backups\Backup_yyyymmdd-Random number\Data\B\B1, etc
    I have figured out how to remove the random numbers but not how to alter the path to how I’d like. Could you offer some advise please, I think I’m missing some vital knowledge as to how the paths are built.

  11. hi, how use “Send Email Settings”? I set email from, email to and $EmailSMTP = ‘smtp.gmail.com’ and it not working. How can i use it?

  12. I ran into an issue that I was able to fix.
    I use PowerShellISE so I can interactively work on scripts and run them easily.

    At the end of the script, I get:

    Exception calling “ReadKey” with “1” argument(s): “The method or operation is not implemented.”
    At C:\Users\\Downloads\BackupScript.ps1:356 char:1
    + $x = $host.UI.RawUI.ReadKey(“NoEcho,IncludeKeyDown”)
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : NotImplementedException

    After some searching, I found that ReadKey does not work in PowerShellISE.
    A solution I found that works in PowerShell and PowerShellISE was at
    https://stackoverflow.com/questions/20886243/press-any-key-to-continue
    I added the following function:
    Function pause ($message)
    {
    # Check if running Powershell ISE
    if ($psISE)
    {
    Add-Type -AssemblyName System.Windows.Forms
    [System.Windows.Forms.MessageBox]::Show(“$message”)
    }
    else
    {
    Write-Host “$message” -ForegroundColor Yellow
    $x = $host.ui.RawUI.ReadKey(“NoEcho,IncludeKeyDown”)
    }
    }

    and replaced the following from the original script, not in the function
    $x = $host.ui.RawUI.ReadKey(“NoEcho,IncludeKeyDown”)

    with
    pause “Press any key to close …”

    Works like a charm.

  13. As the ExcludeDirs are regex parsed I have an issue with directories that contain + characters. I tried to escape them with \ but this did not lead to a success.

    Can you please advise how to properly escape characters?

    Thanks

  14. Hello Michael,
    great thanks for your Script.
    Unfortunately I have a problem.
    The logfile say to me, that the script find 5 files with 0.04 MB to copy but after that, it copies 2 files with 0.00 MB and no file is in the backup folder.
    What is my problem in the script?
    Thanks allot.
    Best regards Alex

  15. Dear Michael,
    I tested the script and it works fine, with exception of some minor flaws. May I ask if you are still maintaining it on GitHub? It would be great if you can have a look at issue #9 “Exclude files does not work properly”. Thanks for the cool script.
    Best
    Jule

  16. Hello, I tried your masterpiece, but aleas I’m totally noob at PS.
    I’m stuck with :

    Count all files and create the Top Level Directories
    Impossible d’appeler une méthode dans une expression Null.
    Au caractère I:\p15.ps1:204 : 9
    + $SumMB += $colItems.Sum.ToString()
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation : (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    There are 0 Files with 0,00 MB of Files to copy

    The settings I used….
    #Variables, only Change here
    $Destination = “i:\ps1″
    #”\\SVATHOME002\Backup$\NB BaseIT” #Copy the Files to this Location
    $Staging = “i:\ps1\Staging”
    $ClearStaging = $true # When $true, Staging Dir will be cleared
    $Versions = “3” #How many of the last Backups you want to keep
    $BackupDirs = “F:\GcodeF”
    #”C:\Users\seimi\Documents” #What Folders you want to backup

    $ExcludeDirs = #This list of Directories will not be copied
    ($env:SystemDrive + “\Users\.*\AppData\Local”)
    #($env:SystemDrive + “\Users\.*\AppData\LocalLow”),
    #”C:\Users\seimi\OneDrive – Seidl Michael\0-Temp”,
    #”C:\Users\seimi\OneDrive – Seidl Michael\0-Temp\Dir2″

    $LogfileName = “Log” #Log Name
    $LoggingLevel = “3” #LoggingLevel only for Output in Powershell Window, 1=smart, 3=Heavy
    $Zip = $true #Zip the Backup Destination
    $Use7ZIP = $false #Make sure it is installed
    $RemoveBackupDestination = $true #Remove copied files after Zip, only if $Zip is true
    $UseStaging = $false #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

    #Send Mail Settings
    $SendEmail = $false # = $true if you want to enable send report to e-mail (SMTP send)
    $EmailTo = ‘test@domain.com’ #user@domain.something (for multiple users use “User01 <user01@example.com>” ,”User02 <user02@example.com>” )
    $EmailFrom = ‘from@domain.com’ #matthew@domain
    $EmailSMTP = ‘smtp.domain.com’ #smtp server adress, DNS hostname.

    Your help would be greatly appreciated (I tried newest 1.5 version with same error…)

  17. Pingback: Powershell Backup Script – TricksDream

  18. Pingback: Powershell Backup Script - SecuredGuide

Leave a Comment

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

*