BackupSQL.ps1

<#PSScriptInfo
 
.VERSION 1.0
 
.GUID 1069276e-50b4-414a-ae8c-b8801445ae7e
 
.AUTHOR Juan Granados
 
.COPYRIGHT 2021 Juan Granados
 
.TAGS SQL Server WID Windows Internal Database backup mail email rotation network share
 
.LICENSEURI https://raw.githubusercontent.com/juangranados/powershell-scripts/main/LICENSE
 
.PROJECTURI https://github.com/juangranados/powershell-scripts/tree/main/SQL%20Server%20Backup
 
.EXTERNALMODULEDEPENDENCIES
 
.RELEASENOTES
    Initial release
#>


<#
.SYNOPSIS
    Full and Log Backup of SQL Server instance databases with SMO
.DESCRIPTION
    Performs Full and Log Backup of SQL Server instance databases with SMO in a zip file and sends an email with the result.
     
    Deletes backups older than n days.
     
    For detailed how to use, run: Get-Help BackupSQL.ps1 -Full
     
    Requisites to run backup in a computer without SQL Server installed
     
    1. Navigate to: https://www.microsoft.com/en-US/download/details.aspx?id=55992 and install SQLSysClrTypes.msi
     
    2. Run from PowerShell
        - Register-PackageSource -provider NuGet -name nugetRepository -location https://www.nuget.org/api/v2
        - Install-Package Microsoft.SqlServer.SqlManagementObjects
     
    3. Run from PowerShell: Install-Module -Name SqlServer
.PARAMETER BackupDirectory
    Directory where zip file will be saved. UNC paths are supported.
    Default "...\My Documents\SQLBackup"
    Example: \\SRV-FS01\Backups\SQL01
.PARAMETER DataBases
    Array of databases to backup.
    If empty all instance databases will be saved.
    Example: BEDB,msdb,model
.PARAMETER Instance
    Instance Name.
    Default: default instance.
    Example: SQLSVR01\BKUPEXEC
.PARAMETER FullBackup
    Copy database and logs
.PARAMETER RetainDays
    Days to keep backups in BackupDirectory. Backups prior to this number of days will be deleted.
    Default: keeps all backups
    Example: 30
.PARAMETER TempDirectory
    Temporary directory to save backups files in order to make a zip file.
    Warning: this folder will be deleted after backup.
    Default "C:\temp\SQLBackup"
    Example: E:\SQLBackupTemp
.PARAMETER SMTPServer
    Sets smtp server in order to sent an email with backup result.
    Default: None
.PARAMETER Recipient
    Array of emails addresses which will receive the backup result.
    Default: None
.PARAMETER Sender
    Email address which will send the backup result.
    Default: None
.PARAMETER Username
    Username in case of smtp server requires authentication.
    Default: None
.PARAMETER Password
    Password in case of smtp server requires authentication.
    Default: None
.PARAMETER SSL
    Use of SSL in case of smtp server requires SSL.
    Default: False
.PARAMETER Port
    Port to connect to smtp server.
    Default: 25
.PARAMETER WriteEvent
    Writes an event with script result in Windows Application Event Log
.EXAMPLE
    Backup default instance databases to a network share
    C:\PS>.\BackupSQL.ps1 -BackupDirectory \\FS-SERVER01\BackupSQL
.EXAMPLE
    Backup WID to a network share
    BackupSQL.ps1 -BackupDirectory "\\MV0SRV-C01\Backups" -Instance "\\.\pipe\MICROSOFT##WID\tsql\query"
.EXAMPLE
    Backup default instance databases to a network share and send an email with result using gmail
    C:\PS>.\BackupSQL.ps1 -BackupDirectory \\FS-SERVER01\BackupSQL -SMTPServer smtp.gmail.com -Recipient jgranados@contoso.com,administrator@contoso.com -Sender backupSQL@gmail.com -Username backupSQL@gmail.com -Password Pa$$W0rd -SSL True -Port 587
.EXAMPLE
    Backup named instance databases to a network share
    C:\PS>.\BackupSQL.ps1 -BackupDirectory \\FS-SERVER01\BackupSQL -Instance SQLSVR01\BKUPEXEC
.EXAMPLE
    Backup default instance databases to a network share, delete from network share files older than a week and
    write result in Windows Application Event
    C:\PS>.\BackupSQL.ps1 -BackupDirectory \\FS-SERVER01\BackupSQL -RetainDays 7 -WriteEvent True
.EXAMPLE
    Backup only specified databases of a named instance
    C:\PS>.\BackupSQL.ps1 -Instance SQLSVR01\BKUPEXEC -DataBases BEDB,msdb,model
.LINK
    https://github.com/juangranados/powershell-scripts/tree/main/SQL%20Server%20Backup
.NOTES
    Author: Juan Granados
#>

    Param(
        [Parameter(Mandatory=$false,Position=0)] 
        [ValidateNotNullOrEmpty()]
        [string]$BackupDirectory=[environment]::getfolderpath("mydocuments") + "\SQLBackup",
        [Parameter(Mandatory=$false,Position=1)] 
        [ValidateNotNullOrEmpty()]
        [string[]]$DataBases="all",
        [Parameter(Mandatory=$false,Position=2)] 
        [ValidateNotNullOrEmpty()]
        [string]$Instance=$env:computerName,
        [Parameter] 
        [switch]$FullBackup,
        [Parameter(Mandatory=$false,Position=4)] 
        [ValidateRange(0,36500)]
        [int]$RetainDays=0,
        [Parameter(Mandatory=$false,Position=5)] 
        [ValidateNotNullOrEmpty()]
        [string]$TempDirectory = "C:\temp\SQLBackup",
        [Parameter(Mandatory=$false,Position=6)] 
        [ValidateNotNullOrEmpty()]
        [string]$SMTPServer="None",
        [Parameter(Mandatory=$false,Position=7)] 
        [ValidateNotNullOrEmpty()]
        [string[]]$Recipient,
        [Parameter(Mandatory=$false,Position=8)] 
        [ValidateNotNullOrEmpty()]
        [string]$Sender,
        [Parameter(Mandatory=$false,Position=9)] 
        [ValidateNotNullOrEmpty()]
        [string]$Username="None",
        [Parameter(Mandatory=$false,Position=10)] 
        [ValidateNotNullOrEmpty()]
        [string]$Password="None",
        [Parameter(Mandatory=$false,Position=11)] 
        [ValidateSet("True","False")]
        [string[]]$SSL="False",
        [Parameter(Mandatory=$false,Position=12)] 
        [ValidateNotNullOrEmpty()]
        [int]$Port=25,
        [Parameter] 
        [switch]$WriteEvent
    )
Import-Module SQLServer -ErrorAction SilentlyContinue
[void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.ConnectionInfo')            
[void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.Management.Sdk.Sfc')            
[void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO')            

# Required if SQL Server 2008 (SMO 10.0).
[void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMOExtended')            
$ErrorActionPreference = "Stop"

# Function to find dabatabase in $Databases[] variable
Function Get-Database {
    Param([string]$Database)
    for($i = 0; $i -le $Databases.length -1; $i++) {
        if ($Databases[$i] -eq $Database) {
            return $true
        }
    }
    return $false
}

# Variable to measure script execution time
$startDTM = (Get-Date)

# Data sumatory
$DataSum = 0

# Timestamp
$timestamp = Get-Date -format yyyy-MM-dd-HH-mm-ss

# Create application event source
if ($WriteEvent) {
    if (!(Get-Eventlog -LogName "Application" -Source "BackupSQL")) {
        New-Eventlog -LogName "Application" -Source "BackupSQL"
    }
}
if ($TempDirectory -eq $BackupDirectory) {
    Write-Output "BackupDirectory can not be the same as TempDirectory. Script can not continue"
    if ($WriteEvent) {
        Write-EventLog �LogName Application �Source "BackupSQL" �EntryType Error �EventID 2 �Message "BackupDirectory can not be the same as TempDirectory. Script can not continue."
    }
    Exit
}
if ($TempDirectory.Chars($TempDirectory.Length - 1) -eq '\') {
        $TempDirectory = ($TempDirectory.TrimEnd('\'))
}
if ($BackupDirectory.Chars($BackupDirectory.Length - 1) -eq '\') {
        $BackupDirectory = ($BackupDirectory.TrimEnd('\'))
}
# Check if backup directory exist and try to create if not
if(!(Test-Path -Path $BackupDirectory)) {
    try {
        New-Item -ItemType directory -Path $BackupDirectory
    } catch {
        Write-Output "$BackupDirectory does not exists and can not be created. Script can not continue"
        if ($WriteEvent) {
           Write-EventLog �LogName Application �Source "BackupSQL" �EntryType Error �EventID 2 �Message "SQL Backup Failed. $BackupDirectory does not exists and can not be created."
        }
        Exit
    }
}      

# Check if local directory exist and try to create if not
if(!(Test-Path -Path $TempDirectory)) {
    try {
        New-Item -ItemType directory -Path $TempDirectory
    } catch {
         Write-Output "$TempDirectory does not exists and can not be created. Script can not continue"
         if ($WriteEvent) {
             Write-EventLog �LogName Application �Source "BackupSQL" �EntryType Error �EventID 2 �Message "SQL Backup Failed.$TempDirectory does not exists and can not be created."
         }
         Exit 
    }
}

try {
    Write-Output "Starting backup"
    # SQL Server
    $srv = New-Object Microsoft.SqlServer.Management.Smo.Server $Instance  
    
    #Disable timeouts monitorization
    $srv.ConnectionContext.StatementTimeout = 0

    # Delete temp folder archives
    Get-ChildItem �Path  $TempDirectory | Remove-Item
    
    # Copy databases
    foreach ($db in $srv.Databases) {
        If( (($DataBases[0] -eq "all") -or (Get-Database $db.Name)) -and ($db.Name -ne "tempdb") ) {            
            $timestamp = Get-Date -format yyyy-MM-dd-HH-mm-ss  
            $backup = New-Object ("Microsoft.SqlServer.Management.Smo.Backup")        
            $backup.Action = "Database"           
            $backup.Database = $db.Name
            Write-Output "Copying $db"          
            $backup.Devices.AddDevice("$TempDirectory\" + $db.Name + "_full_" + $timestamp + ".bak", "File")            
            $backup.BackupSetDescription = "Full backup of " + $db.Name + " " + $timestamp            
            $backup.Incremental = 0            
            # Full backup
            $backup.SqlBackup($srv)     
            # Log backup if database recovery mode is not simple
            If (($db.RecoveryModel -ne 3) -and ($FullBackup))            
            {                     
                $backup = New-Object ("Microsoft.SqlServer.Management.Smo.Backup")            
                $backup.Action = "Log"            
                $backup.Database = $db.Name            
                $backup.Devices.AddDevice("$TempDirectory\" + $db.Name + "_log_" + $timestamp + ".trn", "File")            
                $backup.BackupSetDescription = "Log backup of " + $db.Name + " " + $timestamp            
                # Truncate log prior to backup
                $backup.LogTruncation = "Truncate"
                # Log backup
                $backup.SqlBackup($srv)            
            }            
        }
    }

    # Creation time
    $timestamp = Get-Date -format yyyy-MM-dd-HH-mm-ss

    # Set zip file and delete '\'
    $InstanceName = $Instance -Replace "\\","-"
    $ZipFile = "$BackupDirectory\$timestamp" + "_" + $InstanceName + "_Backup.zip"
    If(Test-path $ZipFile) {
        Remove-item $ZipFile -Force
    }
    Write-Output "Creating zip file in $BackupDirectory"
    # Zip compression
    Add-Type -assembly "system.io.compression.filesystem"
    [io.compression.zipfile]::CreateFromDirectory($TempDirectory, $ZipFile) 
    #Get-Childitem $TempDirectory -Recurse | Write-Zip -IncludeEmptyDirectories -OutputPath $ZipFile -EntryPathRoot $TempDirectory

    # Show information about the size of file copied
    $DataSum = "{0:N3}" -f (((Get-Item $ZipFile).length) / 1MB)
    Write-Output "Backup $ZipFile stored. $DataSum MB copied"
    
    # Show information about the size of file copied on Windows Event Log
    if ($WriteEvent) {
        Write-EventLog �LogName Application �Source "BackupSQL" �EntryType Information �EventID 1 �Message "Backup $ZipFile stored. $DataSum MB copied"
    }
    # Preparing mail sending
    if($SMTPServer -ne "None") {
        #Creating a Mail object
        $msg = new-object Net.Mail.MailMessage

        #Creating SMTP server object
        $smtp = new-object Net.Mail.SmtpClient($SMTPServer,$Port)

        #Email structure
        $msg.From = $Sender
        $msg.ReplyTo = $Sender
        ForEach($mail in $Recipient) {
            $msg.To.Add($mail)
        }
        if ($Username -ne "None" -and $Password -ne "None") {
                $smtp.Credentials = new-object System.Net.NetworkCredential($Username, $Password)
        }
        if ($SSL -ne "False") {
                $smtp.EnableSsl = $true 
        }
    }
    # Sending email with backup result
    if ($SMTPServer -ne "None") {
        #Email subject
        $msg.subject = $Instance + " SQL Backup Success"
        #Email body
        $msg.body = "Backup $ZipFile stored. $DataSum MB copied"
        #Sending email
        try{
            Write-Output "Sending email"
            $smtp.Send($msg)
            Write-EventLog �LogName Application �Source "BackupSQL" �EntryType Information �EventID 4 �Message "Success backup result sent to $Recipient"
           } catch {
            # Write error on application event log
            if ($WriteEvent) {
                Write-EventLog �LogName Application �Source "BackupSQL" �EntryType Error �EventID 3 �Message "Error sending mail. " + "Exception Type: $($_.Exception.GetType().FullName)" + ". Exception Message: $($_.Exception.Message)"
            }
            write-host "Caught an exception:" -ForegroundColor Red
            write-host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red
            write-host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red 
        }
    }
    # Delete temp directory
    Write-Output "Deleting folder $TempDirectory"         
    Remove-Item -Recurse -ErrorAction SilentlyContinue -Confirm:$false �Path $TempDirectory
    
    # Delete old files from backup directory
    if ($RetainDays -ne 0) {
        get-childitem $BackupDirectory -recurse | where {$_.lastwritetime -lt (get-date).adddays(-$RetainDays) -and -not $_.psiscontainer} |% {remove-item $_.fullname -force}
    }
    
    # Get execution time
    $endDTM = (Get-Date)
    
    Write-Output "Execution time: $(($endDTM-$startDTM).hours) hours $(($endDTM-$startDTM).Minutes) minutes $(($endDTM-$startDTM).Seconds) seconds"
} catch {
    write-host "Caught an exception:" -ForegroundColor Red
    write-host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red
    write-host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red
    # Delete temp directory
    Remove-Item -Recurse -ErrorAction SilentlyContinue -Confirm:$false �Path $TempDirectory
    Write-Host "Script can not continue."
    # Write error on application event log
    if ($WriteEvent) {
        Write-EventLog �LogName Application �Source "BackupSQL" �EntryType Error �EventID 2 �Message "SQL Backup Failed" + "Exception Type: $($_.Exception.GetType().FullName)" + ". Exception Message: $($_.Exception.Message)"
    }
    # Sending email with backup result
    if ($SMTPServer -ne "None") {
        #Email subject
        $msg.subject = $Instance + " SQL Backup Error"
        #Email body
        $msg.body = "SQL Backup Failed" + "Exception Type: $($_.Exception.GetType().FullName)" + ". Exception Message: $($_.Exception.Message)"
        #Sending email
        Write-Output "Sending email"
        try{
        $smtp.Send($msg)
        Write-EventLog �LogName Application �Source "BackupSQL" �EntryType Information �EventID 4 �Message "Error backup result sent to $Recipient"
        } catch {
            # Write error on application event log
            if ($WriteEvent) {
                Write-EventLog �LogName Application �Source "BackupSQL" �EntryType Error �EventID 3 �Message "Error sending mail. " + "Exception Type: $($_.Exception.GetType().FullName)" + ". Exception Message: $($_.Exception.Message)"
            }
            write-host "Caught an exception:" -ForegroundColor Red
            write-host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red
            write-host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red
        }
    }
}