Farm-Backup.ps1

<#
.SYNOPSIS

Perform FULL or DIFFERENTIAL SharePoint Farm backups with or without Site Collection backups.

.DESCRIPTION

 Perform Site Collection backups and FULL or DIFFERENTIAL SharePoint Farm backups. On completion emails spbackup.log (SharePoint Backup Log) to nominated
 recipients. When run as scheduled task, it generates a farm backup based on selections made in the accompanying XML file. The script will also delete
 the backups older than a specified number of days, allowing for storage management.

When backing up your SharePoint 2010 farm you MUST take into account the following:

 1.  Central Admin aplication pool account must have read/write access to the backup share.
 2.  Executive account (account running backup) requires read/write access to the backup share.
 3.  The account that runs the SharePoint 2010 Timer Job (SPTimerV4.exe) service requires read/write access to the backup share
 4.  SQL Service account must have read/write access to the backup share.
 5.  Executive account (account running backup) requires dbcreator and SecurityAdmin roles on farm SQL server.

     More Information: Account permissions and security settings (SharePoint Server 2010) - http://technet.microsoft.com/en-us/library/cc678863.aspx

NOTE: In most situations the Central Admin application pool account, the Executive account & the account that runs the SharePoint 2010 Timer
Job (SPTimerV4.exe)is the Farm Administrators account.

 6.  When running a farm backup using Windows PowerShell, the account you're running it as must have read/write
     access to the location of the backups.
 7.  The backup location must be accessible from the SharePoint machine the backup is running on.
 8.  The backup location must be accessible from the SQL instance that SharePoint is trying to back up.

     NOTE: This is why all the examples are UNCs, \\server\share, and not local paths, C:\backups

 9.  All -eventID values in this script are set to -eventID 1001. You should amend the -eventID value to suit your requirements. A custom SCOM monitor can
  then be created to alert on the specified event ID.
 10. The more threads you specify, the more resources that backup operation. However, each thread is reported individually in the log
files, so using fewer threads makes interpreting the log files easier. By default, one thread is used. The maximum number of threads available is 10.

More Information: Backup and recovery best practices (SharePoint Server 2010) - http://technet.microsoft.com/en-us/library/gg266384.aspx

 11. If required, the backup share and the script Farm-Backup.ps1 can be on a separate servers. The script must however run on a server with SharePoint installed.
 12. The script does not assume the shared directory hosted on the same server as the script and so as a result, uses a protracted method to determine storage availablity.
 13. Backups more than [$days] old are deleted conserving the amount of storage space used and ensure drive used does not run out of space.
 14. Executive account requires local administrator rights on the server.


Definitions:

 1. Backup Job Report - The logfile generated by the script to log progress and failure of the script itself.
Filename: <dd-MMM-yyyy>-backupjobreport.log
Location:
 2. SharePoint Backup Log - The logfile generated by the backup providing a detailed report of the backup.
Filename: spbackup.log
Location:
 3. spbrtoc.xml - The SharePoint Backup Restore Table of Contents XML

.PARAMETER

NONE

.LINKS
   
This script was posted to:  http://spfarmbackup.codeplex.com

Restore a farm (SharePoint Server 2010) at TechNet - http://technet.microsoft.com/en-us/library/ee428314.aspx
Compress Files with Windows PowerShell - http://blogs.msdn.com/b/daiken/archive/2007/02/12/compress-files-with-windows-powershell-then-package-a-windows-vista-sidebar-gadget.aspx
SPFarm.DiskSizeRequired Property - http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spfarm.disksizerequired.aspx
SharePoint Foundation 2010 Backup and Recovery at TechNet - http://technet.microsoft.com/en-us/library/cc287896.aspx
SharePoint Server 2010 Backup and Recovery at TechNet - http://technet.microsoft.com/en-us/library/ee662536.aspx
Backup (SharePoint Server 2010) at TechNet - http://technet.microsoft.com/en-us/library/ee428315.aspx
Backup a farm (SharePoint Server 2010) at TechNet - http://technet.microsoft.com/en-us/library/ee428316.aspx

SharePoint 2010 Cmdlets:
   
Backup-SPFarm - http://technet.microsoft.com/en-us/library/ff607881.aspx
Backup-SPSite - http://technet.microsoft.com/en-us/library/ff607901.aspx
    Merge-SPLogFile - http://technet.microsoft.com/en-us/library/ff607721.aspx
    Get-SPFarm - http://technet.microsoft.com/en-us/library/ff607966.aspx
Set-SPSiteAdministration - http://technet.microsoft.com/en-us/library/ff607619.aspx
Get-SPBackupHistory - http://technet.microsoft.com/en-us/library/ff607871.aspx

Windows Command-Line tools:

Scheduled Tasks (Schtasks) - http://technet.microsoft.com/en-us/library/cc725744(v=ws.10).aspx
Appcmd.exe (IIS 7) - http://technet.microsoft.com/en-us/library/cc772200(v=ws.10).aspx

.NOTES
File Name - Farm-Backup.ps1
Version - v2.3
    Author     - Darren Marsden
Blog - http://www.darrenmarsden.com
Email - dm@darrenmarsden.com
   
Requires   - PowerShell v2.0
- SharePoint 2010 Management Shell
      - Folder structure to include:
<Drive:> FarmBackups (Shared)
\Farm
\Sites
\IIS
\14Hive
\Logs
\GAC
\Solutions
- params.xml with required parameters.

Acknowledgements:

Out-Zip function kindly provided by Brian Lalancette of Navantis.

Fixes/Changes:

Version 1.1. - Added Start-Sleep to allow compression tasks time to complete before script exits.
- Added methods to determine whether there is enough storage available for backup.
Version 1.2  - Changed SharePoint site backup filename to cater for both HTTPS & HTTP.
- Changed SharePoint site backup filename to append date/time.
- Added deletion of SharePoint site backup files based on $date parameter.
- Added Backup & compression of web.config only OR in addition to backup of virtual directories.
Version 1.3  - Correct bug in Get-Role method which results in the following error: "Server: $backupServer is not a valid SharePoint (Application) or the
  server. Script MUST be run on a SharePoint (Application) server." when backup share is on remote server.
Version 2.0  - Replaced method of determining if backup share exists to use Test-Path instead of WMI in Get-Folders method.
- Added method to obtain farm database server name.
- Added method to determine version of SQL. -UseSqlSnapshot requires Enterprise Edition.
- Added check to determine whether Filestream is enabled or not.
- Added -UseSqlSnapshot parameter when backing up sites if SQL verion supports usage.
- Complete re-write of Backup-Sites method to cater for 'Operation is not valid due to the current state of the object.' error.
- Added method to determine whether SP1 is installed to cater for 'Operation is not valid due to the current state of the object.'
  error.
- Added method to get lock state of site collections and store in hashtable (if not using -UseSqlSnapshot parameter)
- Added method to set lock state of site collections back to state stored in hashtable (if not using -UseSqlSnapshot parameter)
- Corrected error: 'Measure-Object : Property "length" cannot be found in any object(s) input' which occurs in Get-BackupStorageUsed
  method when backup folders are newly created and are empty.
- Added method to create folders to allow for re-use in other methods.
- Minor syntax changes, addtional comments.
Version 2.1  - Correct bug in Load-BackupTocXML method which occurs when script is run for first time.
- Changed typographical error in Start-FarmBackup method. if ($backupSites -eq $false) changed to if ($backupSites -eq $true)
Version 2.2  - Added the ability to export Solutions (WSP) from solution gallery.
- Added the ability to inlcude/exclude My Sites from the sites backup.
- Added method to determine amount of storage required for sites backup. Script will now determine total required for sites & farm
  backup and determine whether there is sufficient storage for backup of both sites and farm.
- Added option to bypass storage warning when running a DIFFERENTIAL backup (based on $minStoragePercentFree threshold).
- Corrected problem with backup option [0] FULL: Sun DIFF: Mon-Sat. When clean up process deletes FULL backup as part of clean-up
  process (this maybe because you have set the days to retain threshold too low due to storage limitations) which resulted in the
  following error: "A differential backup cannot be started or restored because no full backup exists". On receipt of this error,
  script will retry backup with a FULL backup.
- Removed method Get-LastModifiedFarmBackup and replaced with method Get-BackupHistory which uses the Get-SPBackupHistory cmdlet.
- Cleaned up Clear-HistoricalFarmBackup method and renamed to Clean-BackupHistory
- Changed message body of summary e-mail to include errors/warnings.
- Ammended Get-Role method to include role SingleServer for Standalone installations.
- Added Is-Foundation method to determine whether SharePoint edition is Foundation, Standard or Enterprise to prevent errors when running script
  on SharePoint Foundation 2010 as Foundation does not have MySite functionality.
- Added Mount-NetworkDrive and Dismount-NetworkDrive methods to map and unmap network drives.
- Replaced net use with Mount-NetworkDrive and Dismount-NetworkDrive methods

Version 2.3  - Add method Check-MySiteHost to determine whether My Site Host is available. Normally available when:
a. User Profile Service Application is provisioned and User Profile Synchronization Service is started.
b. My Site Host Location (URL) is correctly set in 'Setup My Sites'.
- Improved error checking for User Profile Service issues.
- Reworked Is-MySite method and renamed to Get-MySiteHost.
- Changed how determining whether My Site is actually My Site. Now uses WebApplication.Id property instead of -match regular expression.
- Corrected problem which occurs when computer name is FQDN and not NetBIOS name.
- Ammended Get-Role method to cater for scenario where computer name is FQDN.
- Removed Is-Foundation method.
- Reworked Get-LockState & Set-LockState methods. Methods now run pre and post individual site backup to mitigate potential for
  site being left in Locked state.
- Modified Backup-ULSLogs method.
- Added method Convert-DateTime to cater for scenario where farm locale is not the same as system locale. This would result in entries
  into the spbrtoc.xml being entered using the Farm locale which in turn may result in the Farm backup directory being deleted as the
  date format was different.
 
  Example: Farm DateTime format: M/d/yyyy h:mm:ss tt (en-US)
  System DateTime format: dd.MM.yyyy HH:mm:ss (de-CH)
 

.EXAMPLE

--------------------------------------------------------------- EXAMPLE 1 ---------------------------------------------------------------

PowerShell -noprofile ".\Farm-Backup.ps1"

When run from .bat file this command will generate a backup of a SharePoint 2010 environment based on settings in params.xml. This script is
intended to be run as a Scheduled Task.

#>

<#

DISCLAIMER: This script should be thoroughly tested before use in a Production environment. You have a royalty-free right to use,
modify, reproduce, and distribute this script file in any way you find useful, provided that you agree that the creator, owner  
above has no warranty, obligations, or liability for such use.                                                                  

#>

#Region Setup Paths

# Check if the execution policy is set to Unrestricted  
try
{ $policy = Get-ExecutionPolicy  

if($policy -ne "Unrestricted")
{  
   Set-ExecutionPolicy "Unrestricted"
}
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Warning: A problem occurred whilst attempting to set the the Execution Policy. Reason: $errText"
$GLOBAL:hasErrors = $true
}

# Get computer name (LOCAL)
$GLOBAL:computer = $env:computerName.ToUpper()
# http://msdn.microsoft.com/en-us/library/system.net.dns.aspx
# Get computer name (FQDN)
$computerFQDN = ([system.net.dns]::GetHostByName("localhost")).hostname.ToUpper()
#http://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectory.domain.getcomputerdomain.aspx
$computerDomain = ([System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain()).Name.ToUpper()
# Get Start Time
$startDTM = (Get-Date)
#Get the path that this script is running in
$scriptRoot = Split-Path (Resolve-Path $myInvocation.MyCommand.Path)
# Location of XML configuration file
$paramsXML = Join-Path $scriptRoot "\params.xml"
# Location of Logfile
$jobReport = $scriptRoot + "\" + $(Get-Date -format dd-MMM-yyyy) + "-backupjobreport.log"
# Set the Event Log Source type
$eventSourceType = "SharePoint Backup"
# Determine current day of week
$date = Get-Date
$dayofweek = $date.DayOfWeek
# Set SharePoint 14 Hive directory
$14hive = "C:\Program Files\Common Files\Microsoft Shared\web server extensions\14\"
# Set GAC (Global Assembly Cache) directory
$gac = "C:\Windows\assembly\"
# Set minimum amount of storage that MUST be free (for DIFFERENTIAL backups)
[int64]$minStoragePercentFree = 15 # Percentage %
# Set Error state to FALSE
$GLOBAL:hasErrors = $false

#EndRegion

#Region Check for params.xml & load parameters
function Load-ParamsXML
{

Log-InformationEvent "SharePoint site/farm backup job started at: $startDTM"
Write-Log "Info: SharePoint site/farm backup job has started."

if (!(Test-Path $paramsXML))
{
Write-Log "Error: Required file params.xml not found. File location is set to: $paramsXML. Action: Check that the params.xml exists and/or the path to the file is correct."
Log-ErrorEvent "SharePoint site/farm backup job exited prematurely with error(s). `n`nReason: Required file paramsXML not found. Check that the file paramsXML exists and/or the path to the file is correct."
Exit
}
else
{
[xml]$xml = (Get-Content $paramsXML)
Write-Log "Info: Required file params.xml found. Action: Loading XML."
}

$GLOBAL:backupServer = $xml.backup.params.backupserver # Backup Server (Required if location of backup share 'FarmBackups$' not on Local Server)

if ($backupServer -eq "")
{
$GLOBAL:backupServer = $Computer
Write-Log "Info: Backup Server is not set in XML. Script will default to local server: $Computer"
}

$GLOBAL:smtpServer = $xml.backup.params.smtpserver # Mail Server
$GLOBAL:sendEmail = $xml.backup.params.sendemail # Send e-mail: TRUE or FALSE
$GLOBAL:emailFrom = $xml.backup.params.emailfrom # Sender address (From:)
    $GLOBAL:emailTo = $xml.backup.params.emailto # Email recipient (To:)
$GLOBAL:emailCC = $xml.backup.params.emailcc # Email recipient (Cc:)
$GLOBAL:env = $xml.backup.params.environment # Example: Production/Stage/Integration
$GLOBAL:backupIIS = $xml.backup.params.backupiis # Perform backup of IIS MetaData & Virtual Directories: TRUE or FALSE
$GLOBAL:backupWebConfigOnly = $xml.backup.params.backupwebconfigonly # Perform backup of Web.Config Only: TRUE or FALSE
$GLOBAL:exportSolutions = $xml.backup.params.exportsolutions # Perform backup (Export) of Solutions (WSP): TRUE or FALSE
$GLOBAL:backup14Hive = $xml.backup.params.backup14hive # Perform backup of 14 Hive: TRUE or FALSE
$GLOBAL:backupGAC = $xml.backup.params.backupgac # Perform backup of GAC (Global Assembly Cache): TRUE or FALSE
$GLOBAL:backupULSLogs = $xml.backup.params.backupulslogs # Perform backup of SharePoint Diagnostic Logs: TRUE or FALSE
$GLOBAL:backupConfigOnly = $xml.backup.params.backupconfigonly # Perform Configuration Only backup TRUE or FALSE
$GLOBAL:backupFullDays = $xml.backup.params.backupfulldays # Day(s) on which FULL backup will run if Backup Option: [1] selected
$GLOBAL:backupOption = $xml.backup.params.backupoption # Backup Option: [0] FULL: Sun
# DIFF: Mon-Sat
# [1] FULL: Selected day(s) as stipulated in params.xml
# DIFF: Remaining Days
# [2] FULL: Everyday
#
# NOTE: if option [2] set then script will ignore $backupFullDays
$GLOBAL:backupSites = $xml.backup.params.backupsites # # Backup SharePoint sites: TRUE or FALSE
$GLOBAL:includeMySites = $xml.backup.params.includemysites # Include MySites (User sites) from sites backup: TRUE or FALSE
$GLOBAL:backupThreads = $xml.backup.params.backupthreads # No of Threads (Option: 1 -10 Default: 1)
$GLOBAL:days = $xml.backup.params.daystoretain # No of days backups that will be retained.
$GLOBAL:backupShare = $xml.backup.params.backupshare # Backup share name (Default: FarmBackups$)
$GLOBAL:backupDirectory = Join-Path "\\$backupServer\" $backupShare # Root backup directory
$GLOBAL:siteBackupDirectory = Join-Path $backupDirectory "\Sites\" # Backup directory for sites
$GLOBAL:farmBackupDirectory = Join-Path $backupDirectory "\Farm\" # Backup directory for farm
$GLOBAL:logsBackup = Join-Path $backupDirectory "\Logs\" # Directory for Logs file(s)
$GLOBAL:14hiveBackupDirectory = Join-Path $backupDirectory "\14Hive\" # Backup directory for 14 Hive
$GLOBAL:gacBackupDirectory = Join-Path $backupDirectory "\GAC\" # Backup directory for GAC (Global Assembly Cache)
$GLOBAL:iisBackupDirectory = Join-Path $backupDirectory "\IIS\" # Backup directory for IIS MetaData & Web Root
$GLOBAL:solutionsDirectory = Join-Path $backupDirectory "\Solutions\" # Backup directory for Solutions

if (($days -lt 1) -or ($days -eq 1))
{
Halt-OnError "No. of days backups to be retained is set to [$days] day(s). Value must be greater than [1] day. Action: Amend backups to be retained value to greater than [1] day and restart backup job."
}

if($backupSites -eq $true)
{
Write-Log "Warning: The backup of site(s) is set to TRUE. As a result, depending on the number of site(s) and thier size, the duration of the farm backup may increase considerably."
}
}
#EndRegion

#Region Mount/Dismount Network Drives
function Mount-NetworkDrive
{
param ([string]$drive, [string]$networkPath)

try
{

Write-Log "Info: Mounting drive $drive to $networkPath"
$objNet = New-Object -ComObject "WScript.Network" -ErrorAction Stop
$objNet.MapNetworkDrive(($drive), $networkPath)
Start-Sleep -Milliseconds 500
Write-Log "Info: Drive $drive successfully mounted."
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Warning: A problem occurred whilst attempting to mount the network drive. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}

function Dismount-NetworkDrive
{
param ([string]$drive)

try
{
Write-Log "Info: Dismounting drive $drive."
$objNet = New-Object -ComObject "WScript.Network" -ErrorAction Stop
$objNet.RemoveNetworkDrive($drive, $true)
Write-Log "Info: Drive $drive successfully dismounted."
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Warning: A problem occurred whilst attempting to dismount the network drive. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Check for next available drive letter
function Get-DriveLetter
{
# Determine the next free drive letter available and map drive to backup share.

try
{
<#

Credit to: Jeffrey Hicks of http://jdhitsolutions.com for his version of 'get next drive letter'.

Replaced my version:

$drives = [char[]]'CDEFGHIJKLMNOPQRSTUVWXYZ' | ? { (Get-PSDrive $_ -ErrorAction Stop) -eq $null }
$GLOBAL:driveLetter = $drives[0]
$GLOBAL:driveLetter = $driveLetter + ":" # Append the colon to the drive letter.

With the version taken from here: http://jdhitsolutions.com/blog/2012/04/friday-fun-get-next-available-drive-letter/

#>

$driveLetters=[char[]](67..90)
$devices = Get-WmiObject Win32_LogicalDisk | Select -expand DeviceID -ErrorAction Stop
$driveLetter = $driveLetters | Where {$devices -notcontains "$($_):"} | Select -first 1

$GLOBAL:driveLetter = $driveLetter + ":" # Append the colon to the drive letter.

# Maps drive to the backup share to allow script to determine amount of storage available.
Mount-NetworkDrive $GLOBAL:driveLetter $backupDirectory
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Warning: A problem occurred whilst determining the next available drive letter. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#Endregion

#Region Check for the existence of the required backup share. Check/Create the required backup directories
function Get-Folders
{
# if backup share not found script will exit.
# if backup directories not found script will attempt to create them.

try
{
if (!(Test-Path "\\$backupServer\$backupShare"))
{
Halt-OnError "Backup share \\$backupServer\$backupShare not found. Action: Check that the share: $backupShare on server: $backupServer exists and is accessible."
}
else
{
Write-Log "Info: Backup share \\$backupServer\$backupShare is present and accessible. Action: No action required."
}
}
catch
{
$errText = $error[0].Exception.Message
Halt-OnError "Checking for existence of $backupShare failed. Reason: $errText"
}

$folders = @($farmBackupDirectory, $siteBackupDirectory, $iisBackupDirectory, $14hiveBackupDirectory, $logsBackup, $gacBackupDirectory, $solutionsDirectory)

foreach ($folder in $folders)
{
if (!(Test-Path $folder))
{
Create-Folder $folder
}
else
{
Write-Log "Info: $folder present. Action: No action required."
}
}
}
#EndRegion

#Region Load the SharePoint snap-in for PowerShell
function Load-Snapin
{

$snapin = (Get-PSSnapin -name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue)

if ($snapin -ne $null) {
Write-Log "Info: SharePoint Snap-in is loaded. Action: No action required."
}
else
{
try
{
Write-Log "Info: SharePoint Snap-in not found. Action: Loading SharePoint Snap-in."
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction Stop
}
catch
{
$errText = $error[0].Exception.Message
Halt-OnError "Loading of SharePoint Snap-in failed. Reason: $errText"        
}
}
}
#EndRegion

#Region Determine the amount of storage available for backup files
function Get-BackupStorageAvailable
{

Write-Log "Info: Determining if there is sufficient storage available for farm backup."

try
{
# Checks for the available space on the drive
$driveInfo = Get-WmiObject -class win32_LogicalDisk -ErrorAction Stop | Where {$_.DeviceId -eq $driveLetter} -ErrorAction Stop
$GLOBAL:backupStorageAvailable = ([math]::round(($driveInfo.FreeSpace / 1GB),2))
$GLOBAL:backupStorageSize =  ([math]::Round(($driveInfo.Size / 1GB),2))
$GLOBAL:backupStorageFreePercent = ([math]::Round(([int64]$backupStorageAvailable / [int64]$backupStorageSize * 100),2))
       
        [int64] $GLOBAL:backupStorageFreePercent = [System.Convert]::ToInt64($GLOBAL:backupStorageFreePercent)

Write-Log "Info: Total storage available for farm backup: $GLOBAL:backupStorageAvailable GB ($GLOBAL:backupStorageFreePercent%)."
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Warning: A problem occurred whilst determining the total storage available. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Determine the total storage required for a farm backup
function Get-ContentStorageRequired
{
# SPFarm.DiskSizeRequired Property - http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spfarm.disksizerequired.aspx

$GLOBAL:contentStorageUsed = 0

try
{
$ContentDatabases = Get-SPDatabase -ErrorAction Stop | Select Name, @{label="DiskSizeRequired";Expression={([math]::Round(($_.DiskSizeRequired / 1GB),2))}}
        #$ContentDatabases = Get-SPDatabase -ErrorAction Stop | Select Name, @{label="DiskSizeRequired";Expression={([math]::round($_.DiskSizeRequired/1024/1024/1024, 1))}}

foreach($ContentDatabase in $ContentDatabases)
{
$GLOBAL:contentStorageUsed += $ContentDatabase.DiskSizeRequired
}

Write-Log "Info: Total storage required for farm backup: $GLOBAL:contentStorageUsed GB."

}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Warning: A problem occurred whilst determining the total storage required for the farm backup. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Return storage used for all Site collections in Farm
function Get-SiteStorageRequired
{  

$sumUsed = 0

Check-MySiteHost

if($includeMySites -eq $false)
    {
       Write-Log "Info: Include MySites is set to: FALSE. Skipping calculation of storage used by MySites."
}

# Loop through all web applications (specify filter criteria here if you want to filter web applications out)
foreach ($webApplication in Get-SPWebApplication -IncludeCentralAdministration:$false -ErrorAction Stop)
{
try
   {
       foreach($site in $webApplication.sites)
   {
                if($includeMySites -eq $false)
{
if($site.WebApplication.Id -eq $MySiteHost.WebApplication.Id)
{
continue;
}
}

$sumUsed = $site.usage.storage
       $siteStorageUsed += $sumUsed
}
   }
   catch
  {
       $errText = $error[0].Exception.Message
Write-Log "Warning: A problem occurred whilst determining the amount of storage required for the site(s) backup. Reason: $errText"
$GLOBAL:hasErrors = $true
   }
}

$GLOBAL:siteStorageUsed = ([math]::Round(($siteStorageUsed / 1GB),2))
Write-Log "Info: Total storage required for site(s) backup: $GLOBAL:siteStorageUsed GB."
}
#EndRegion

#Region Determine the amount of storage currently used by the backup directory
function Get-BackupStorageUsed
{
try
{
      $folders = Get-ChildItem $backupDirectory | Where{$_.PSiscontainer -eq $true}
     
  foreach ($folder in $folders)
      {
$GLOBAL:backupStorageUsed = (Get-ChildItem $folder.FullName -recurse | Measure-Object -ErrorAction Stop -property length -sum)
$GLOBAL:backupStorageUsed = ([math]::Round(($storageUsed.sum / 1GB),2))
}
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Warning: A problem occurred whilst determining the amount of storage currently used by the backup directory. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Connect to SharePoint farm
function Get-Farm
{
Write-Log "Info: Attempting to connect to SharePoint farm."

try
{
# Connect to the farm
$GLOBAL:Farm = Get-SPFarm -ErrorAction Stop

if (($Farm.Status -ne 'Online') -or ($Farm -eq $null))
{
Halt-OnError "The farm appears to be offline or is inaccessible. Action: Check & confirm farm is online and accessible."
}
else
{
$FarmName = $Farm.Name
Write-Log "Info: Successfully connected to SharePoint farm: $FarmName"
}
}
catch
{
$errText = $error[0].Exception.Message
Halt-OnError "Unable to access farm. Reason: $errText"
}
}
#EndRegion

#Region Check to determine whether SP1 is installed on farm.
function Is-SP1
{
try
{
$GLOBAL:FarmBuild = $Farm.BuildVersion.Build # Put in Global Variable for use later.
$GLOBAL:FarmVersion = $Farm.BuildVersion # Put in Global Variable for use later.

if ($GLOBAL:FarmBuild -ge 6029)
{
Write-Log "Info: SharePoint farm version is: $FarmVersion Build: $FarmBuild  SharePoint Server 2010 SP1 or greater is installed."
return $true
}
else
{
Write-Log "Info: SharePoint farm version is: $FarmVersion Build: $FarmBuild. SharePoint Server 2010 SP1 or greater is not installed."
return $false
}
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Error: Unable to determine SharePoint farm build. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Check script is running on a valid SharePoint Application, SingleServer or WebFrontEnd server
function Get-Role
{
# http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spserverrole.aspx
    Write-Log "Info: Checking server the script is running on is a valid SharePoint (Application/WebFrontEnd/SingleServer) server."
   
$roleFound = $false

try
{
foreach ($Server in $Farm.Servers)
{
$svrName = $Server.Name
$svrRole = $Server.Role

if (($svrName -eq $computer) -or ($svrName -eq $computerFQDN) -and ($svrRole -ne "Invalid"))
{
$roleFound = $true
break
}
}
if ($roleFound -eq $false)
{
Halt-OnError "Server: $Computer is not a valid SharePoint (Application/WebFrontEnd/SingleServer). Script MUST be run on a SharePoint (Application/WebFrontEnd/SingleServer) server. Action: Check SharePoint is installed and that the SharePoint 2010 Management Shell is available."
}
else
{
Write-Log "Info: Server: $svrName is a valid $svrRole server. Action: No action required."
}
}
catch
{
$errText = $error[0].Exception.Message
Halt-OnError "Unable to determine server role. Reason: $errText"
}
}
#EndRegion

#Region Get farm database server
function Get-FarmDbServer
{
try
{
#$database = Get-SPDatabase -ErrorAction Stop
       
        $database = Get-SPDatabase -ErrorAction Stop | Where-Object {$_.Name -Match "Config"}
        $GLOBAL:FarmDBServer = $database.Server.Name

        #$GLOBAL:FarmDBServer = $database[0].Server.Name
#$GLOBAL:FarmDBServer = $database[0].Server.Name + "\" + $database[0].ServiceInstance.Name

Write-Log "Info: Farm database server: $FarmDBServer"
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Warning: Unable to determine farm database server. Reason: $errText"
$GLOBAL:hasErrors = $true

}
}
#EndRegion

#Region Determine whether the version of SQL we are running supports the use of the -UseSqlSnapshot parameter.
function Is-SnapShotCompatible
{

$sqlConnection = New-Object -TypeName System.Data.SqlClient.SqlConnection
$sqlCommand = New-Object -TypeName System.Data.SqlClient.SqlCommand

try
{
$sqlConnection.ConnectionString = "Server=$FarmDBServer;Integrated Security = True"

Write-Log "Info: Attempting to access to farm database server: $FarmDBServer"

$sqlConnection.Open() | Out-Null

Write-Log "Info: Successfully connected to farm database server: $FarmDBServer"

$sqlQuery = "SELECT SERVERPROPERTY('ProductVersion') as Version,
   SERVERPROPERTY('Edition') as Edition,
SERVERPROPERTY('FilestreamEffectiveLevel') as Filestream"

$sqlCommand.CommandTimeout = 120
$sqlCommand.Connection = $sqlConnection
$sqlCommand.CommandText = $sqlQuery
$sqlCommand.ExecuteNonQuery() | Out-Null
# Run the query
$result = $sqlCommand.ExecuteReader()
# Move pointer to the first row
$result.Read() | Out-Null

# Get SQL Version
$sqlVersion = $result.GetValue(0)
# Get SQL Edition
$sqlEdition = $result.GetValue(1)

<#

Get FilestreamEffectiveLevel

0 - Disabled
1 - TSQL Access
2 - TSQL and Win32 Access
3 - TSQL and Remote Win32 API Access

#>

$sqlFilestreamEffectiveLevel = $result.GetValue(2)

Write-Log ("Info: SQL Server Version: {0}" -f $result.GetValue(0))
Write-Log ("Info: SQL Server Edition: {0}" -f $result.GetValue(1))
     
[int]$sqlMajorVersion,[int]$sqlMinorVersion,[int]$sqlBuild,$null = $sqlVersion -split "\."

<#
SharePoint 2010 requires one of the following versions of SQL:

10.50.1600.1 - SQL Server 2008 R2 RTM or Higher
10.00.2714   - SQL Server 2008 SP1 Cumulative Update 2 or Higher
9.00.4220    - SQL Server 2005 SP3 Cumulative Update 3 or Higher

More Information: http://support.microsoft.com/kb/976215
#>

switch ($sqlFilestreamEffectiveLevel)
{
0
{
Write-Log ("Info: SQL Server Filestream Effective Level: [{0}" -f $result.GetValue(2) + "]. Filestream setting: Disabled")
}
1
{
Write-Log ("Info: SQL Server Filestream Effective Level: {0}" -f $result.GetValue(2) + ".Filestream setting: TSQL Access")
}
2
{
Write-Log ("Info: SQL Server Filestream Effective Level: {0}" -f $result.GetValue(2) + ".Filestream setting: TSQL and Win32 Access")
}
3
{
  Write-Log ("Info: SQL Server Filestream Effective Level: {0}" -f $result.GetValue(2) + ".Filestream setting: TSQL and Remote Win32 API Access")
}
}

$sqlConnection.Close()

switch ($sqlMajorVersion)
{
  9
{
if ($sqlBuild -ge 4220)
{
$Version = "SQL Server 2005 SP3 CU3 or Higher"
$IsValidSqlVersion = $false
}
}
 10
{
if (($sqlMinorVersion -eq 0) -and ($sqlBuild -ge 2714))
{
$Version = "SQL Server 2008 SP1 CU2 or Higher"
$IsValidSqlVersion = $true
}
elseif ($sqlMinorVersion -ge 5)
{
$Version = "SQL Server 2008 R2 RTM or Higher"
$IsValidSqlVersion = $true
}
}
  default
{
Write-Log "Info: Unable to determine version of SQL Server."
$IsValidSqlVersion = $false
}
}

<#
Determine whether version of SQL Server is Enterprise Edition.

-UseSqlSnapshot requires 10.00.2714 SQL Server 2008 (Enterprise Edition) SP1 Cumulative Update 2 or Higher
to take database snapshots.

More Information: http://technet.microsoft.com/en-us/library/ff607901.aspx
#>

if (($sqlEdition.Contains("Enterprise Edition")) -and ($IsValidSqlVersion = $true) -and ($sqlFilestreamEffectiveLevel -eq '0'))
{
Write-Log "Info: $Version is installed. -UseSqlSnapshot parameter can be used with this version of SQL."

If (!(Is-SP1))
{
Write-Log "Warning: When using the -UseSqlSnapshot parameter on farms without SP1 installed the following error occurs: Backup-SPSite : Operation is not valid due to the current state of the object. More Info: http://technet.microsoft.com/en-us/library/ee748617.aspx"
}

$GLOBAL:UseSqlSnapshot = $true
}
elseif (($sqlEdition.Contains("Enterprise Edition")) -and ($IsValidSqlVersion = $true) -and ($sqlFilestreamEffectiveLevel -gt '0'))
{
Write-Log "Warning: -UseSqlSnapshot parameter cannot be used when FILESTREAM provider is enabled. More Information: http://technet.microsoft.com/en-us/library/ee748617.aspx"
$GLOBAL:UseSqlSnapshot = $false
}
else
{
Write-Log "Warning: -UseSqlSnapshot parameter cannot be used with this version of SQL. -UseSqlSnapshot requires SQL Server 2008 (Enterprise Edition) SP1 CU2 or Higher and SQL FILESTREAM provider does not support snapshots."
$GLOBAL:UseSqlSnapshot = $false
}
}
catch
{
$errText = $Error[0].ToString()
Write-Log "Warning: There was a problem trying to determine whether this version of SQL is compatible. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Execute farm backup based on options stipulated in params XML file
function Backup-Farm
{
# Execute DIFF/FULL Farm backup based on weekday and backup option parameter
try {
switch ($backupOption)
{
      0
       {      
$Culture =(Get-Culture) # Get current culture of system
[Globalization.CultureInfo]$en_GB = "en-GB" # Create en-GB culture
[Threading.Thread]::CurrentThread.CurrentCulture = $en_GB # Set en-GB culture to main thread

# In a multi-lingual environment scriwpt will accommodate non English entries in <backupfulldays>
# Example: Dimanche (FR) or Sonntag (DE)
if ($dayOfweek -ne "Sunday")
{
$backupFullDays = $date.DayOfWeek
[Threading.Thread]::CurrentThread.CurrentCulture = $Culture
Write-Log "Info: Selected backup option: [$backupOption]. FULL: Selected day(s) as stipulated in params.xml & DIFF: Remaining Days"
$GLOBAL:backupMethod = "DIFFERENTIAL"
Launch-FarmBackup $GLOBAL:backupMethod
        }
else
                {
Write-Log "Info: Selected backup option: [$backupOption]. DIFF: Mon-Sat & FULL: Sun"
$GLOBAL:backupMethod = "FULL"
Launch-FarmBackup $GLOBAL:backupMethod
}
}
      1
       {
          if ($backupFullDays.ToString().ToLower().IndexOf($dayofweek.ToString().ToLower()) -gt -1)
  {
$GLOBAL:backupMethod = "FULL"
Launch-FarmBackup $GLOBAL:backupMethod
  }
  else
               {
$GLOBAL:backupMethod = "DIFFERENTIAL"
Launch-FarmBackup $GLOBAL:backupMethod  
  }
       }
      2
       {
           Write-Log "Info: Selected backup option: [$backupOption]. FULL: Mon-Sun"
$GLOBAL:backupMethod = "FULL"
Launch-FarmBackup $GLOBAL:backupMethod
       }
      default
       {
Halt-OnError "Farm backup did not complete successfully. Farm backup option set to: [$backupOption]. This is not a valid option. Options are:`n
`t[0] FULL: Sun`n
`t    DIFF: Mon-Sat`n
`t[1] FULL: Selected day(s) as stipulated in params.xml`n
`t    DIFF: Remaining days`n
`t[2] FULL: Everyday`n
`tAction: Set a valid option and restart job."
       }
   }
}
catch
    {
$errText =  $error[0].Exception.Message
Write-Log "Error: SharePoint farm $GLOBAL:backupMethod [Configuration Only] backup job failed. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Get lock state of all site collections
function Get-LockState
{
param([string]$Url)

<#
Readonly    - Sets the site collection to read-only
NoAdditions - No new content can be added. Only updates and deletions allowed
Noaccess    - Sets the site collection to unavailable to all users
Unlock      - Sets the site collection/web application to unlocked
#>
 
try
{
$site = Get-SPSite -Identity $site.url

$lockState = "NoAccess"

if ($site.WriteLocked -eq $true)
{
$lockState = "NoAdditions"
}
if ($site.ReadOnly -eq $true)
{
$lockState = "ReadOnly"
}
if (($site.ReadOnly -eq $false) -and ($site.WriteLocked -eq $false))
{
$lockState = "Unlock"
}
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Error: There was an error whilst attempting to get site lock state. Reason: $errText"
$GLOBAL:hasErrors = $true
}

return $lockState
}
#EndRegion

#Region Execute SharePoint site collection(s) backup
function Backup-Sites
{
try
{

<#
IMPORTANT: The following applies to environments that have not been updated to Service Pack 1

When you perform a backup that uses the -UseSqlSnapshot parameter, a backup will be completed successfully. However,
you will see an error similar to the following:

Backup-SPSite : Operation is not valid due to the current state of the object.

At line:1 char:14+ Backup-SPSite <<<< http://site -Path + CategoryInfo : NotSpecified: (:) [Backup-SPSite], InvalidOperationException
+ FullyQualifiedErrorId : System.InvalidOperationException,Microsoft.SharePoint.PowerShell.SPCmdletBackupSite\\yourpath

More Information: http://technet.microsoft.com/en-us/library/ee748617.aspx
#>

Check-MySiteHost

if($includeMySites -eq $false)
{
      Write-Log "Info: Include MySites is set to: FALSE. Skipping backup of MySites."
}

# Loop through all web applications (specify filter criteria here if you want to filter web applications out)
foreach ($webApplication in Get-SPWebApplication -IncludeCentralAdministration:$false -ErrorAction Stop)
{
if ($GLOBAL:UseSqlSnapshot -eq $true)
{
Write-Log "Info: Performing backup of all site collection(s) in web application: $currentWebApplication using -UseSqlSnapshot argument."
}
else
{
Write-Log "Info: Performing backup of all site collection(s) in web application: $currentWebApplication."
}

foreach ($site in $webApplication.Sites)
{
   if($includeMySites -eq $false)
{
if($site.WebApplication.Id -eq $MySiteHost.WebApplication.Id)
{
continue;
}
}

# Replace special characters in url with hyphen
$siteName = $site.Url.Replace("https://","").Replace("http://", "").Replace("/","-") + (Get-Date).ToString('-yyyyMddhhmmss') + ".bak";
$filePath = $siteBackupDirectory + $siteName
$siteUrl = $site.Url.Replace("SPSite Url=","").Replace(".Url", "")

<#
When backing up sites with the Backup-SPSite cmdlet the default action is to lock the site to ensure no modifications
are made during the backup. More Information: http://technet.microsoft.com/en-us/library/ff607901.aspx
#>

$errors = $false

try
{
if ($GLOBAL:UseSqlSnapshot -eq $true)
{
Backup-SPSite -Identity $site.Url -Path $filePath -UseSqlSnapshot -Force -ErrorAction SilentlyContinue
}
else
{
# Get lock state for site collection (store in $siteLockState variable)
$siteLockState = Get-LockState $site.Url

# Backup individual site collection
Backup-SPSite -Identity $site.Url -Path $filePath -Force -ErrorAction Stop

# Set lock state for site collection back to
Set-LockState $site.Url $siteLockState
}
}
catch
{
$errText = $error[0].Exception.Message
if ($errText.Contains("Operation is not valid due to the current state of the object."))
{
continue;
}
else
{
$errors = $true
}
}
finally
{
if ($errors -eq $false)
{
Write-Log  ("Info: Backup of SharePoint site collection $siteUrl successfully completed.")
}
}
}
}
}
catch
    {
$errText = $error[0].Exception.Message
Write-Log "Error: SharePoint sites backup failed. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Set site lock state to original site lock state on completion of site backup
function Set-LockState
{
param ([string]$Url, [string]$siteLockState)
   
    try
{
Set-SPSiteAdministration -LockState $siteLockState -Identity $site.Url -ErrorAction Stop
Write-Log  "Info: Site lock state set to $siteLockState on site $($site.Url)"
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Warning: There was an error whilst attempting to set site lock state. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Execute SharePoint farm backup
function Launch-FarmBackup
{
try
{
$errors = $false

try
{
if ($backupConfigOnly -eq $true)
{
Write-Log "Info: Performing SharePoint $GLOBAL:backupMethod farm [Configuration Only] backup."
Backup-SPFarm -Directory $farmBackupDirectory -BackupMethod $GLOBAL:backupMethod -ConfigurationOnly -BackupThreads $backupThreads -ErrorAction Stop
}
else
{
Write-Log "Info: Performing SharePoint $GLOBAL:backupMethod farm backup."
Backup-SPFarm -Directory $farmBackupDirectory -BackupMethod $GLOBAL:backupMethod -BackupThreads $backupThreads -ErrorAction Stop
}
}
catch
{
$errText =  $error[0].Exception.Message

if ($errText.Contains("A differential backup cannot be started or restored because no full backup exists."))
{
Write-Log "Warning: $errText"
$GLOBAL:backupMethod = "FULL"
Write-Log "Info: Retrying SharePoint farm backup with $GLOBAL:backupMethod option."
Backup-SPFarm -Directory $farmBackupDirectory -BackupMethod $GLOBAL:backupMethod -BackupThreads $backupThreads -ErrorAction Stop
}
else
{
$errors = $true
}
}
finally
{
if ($errors -eq $false)
{
Write-Log "Info: SharePoint farm backup job successfully completed."
}
}
}
catch
    {
$errText = $error[0].Exception.Message
Write-Log "SharePoint farm backup job failed. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Perform 'House-Keeping'. Remove Zip archives & Logfiles older than $days parameter
function Remove-BackupFiles
{
# Deletes old backups based on $days value. $days value stipulates no. of days backups to be retained.

Param([string]$backupType, [string]$targetFolder, [string]$fileExtension)

try
{

$Files = Get-ChildItem $targetFolder -Include $fileExtension -Recurse -ErrorAction Stop | Where {$_.LastWriteTime -lt (Get-Date).AddDays(-$days)}

Write-Log "Info: Attempting to delete $backupType older than [$days] day(s)."

foreach ($file in $files)
{
if ($file -ne $null)
{
Remove-Item -Path $file -force -ErrorAction Stop
Write-Log "Info: File $file older than [$days] day(s). Action: $file deleted."
}
else
{
Write-Log "Info: There are no file(s) older than [$days] day(s). Action: Nothing deleted."
}
}
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Error: There was a problem whilst attempting to delete $backupType older than [$days] day(s) failed. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}

function Clean-Backups
{
# Deletes backup job reports based on $days value.
Remove-BackupFiles "backup job report(s)" $scriptRoot "*.log"
# Deletes SharePoint Site backup(s) based on $days value
Remove-BackupFiles "SharePoint site backup(s)" $siteBackupDirectory "*.bak"
# Deletes 14 Hive backup(s) based on $days value.
Remove-BackupFiles "14 Hive backup(s)" $14hiveBackupDirectory "*.zip"
# Deletes ULS Log backup(s) based on $days value
Remove-BackupFiles "ULS Log backup(s)" $logsBackup "*.log"
# Deletes GAC (Global Assembly Cache) backup(s) based on $days value
Remove-BackupFiles "GAC (Global Assembly Cache) backup(s)" $gacBackupDirectory "*.zip"
# Deletes IIS backup(s) based on $days value
Remove-BackupFiles "IIS backup(s)" $iisBackupDirectory "*.zip"
}
#EndRegion
 
#Region Execute backup and compression of GAC (Global Assembly Cache)
function Backup-GAC
{

$zipSrc = $gac
$zipTgt = $gacBackupDirectory + "GAC-" + $(Get-Date -Format ddMMyyyyHHmmss) + ".zip"

# Compress GAC (Global Assembly Cache) in $gacBackupDirectory.
try
{
Write-Log "Info: Backing up and compressing GAC (Global Assembly Cache) from $gac to $gacBackupDirectory"
Get-Item -Path $zipSrc -ErrorAction Stop | Out-Zip $zipTgt -ErrorAction Stop
Write-Log "Info: Backup and compression of GAC (Global Assembly Cache) from $gac to $gacBackupDirectory succeeded."
}
catch
{
$errText =  $error[0].Exception.Message
Write-Log "Warning: Backup and compression of GAC (Global Assembly Cache) from $gac to $gacBackupDirectory failed. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Execute backup and compression of 14 Hive
function Backup-14Hive
{
$zipSrc = $14hive
$zipTgt = $14hiveBackupDirectory + "14Hive-" + $(Get-Date -Format ddMMyyyyHHmmss) + ".zip"

# Zip 14 Hive to $14hiveBackupDirectory.
try
{
Write-Log "Info: Backing up and compressing 14 Hive from $14hive to $14hiveBackupDirectory"
Get-Item -Path $zipSrc -ErrorAction Stop | Out-Zip $zipTgt -ErrorAction Stop
Write-Log "Info: Backup and compression of 14 Hive from $14hive to $14hiveBackupDirectory succeeded."
}
catch
{
$errText =  $error[0].Exception.Message
Write-Log "Warning: Backup and compression of 14 Hive from $14hive to $14hiveBackupDirectory failed. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Execute backup and compression of IIS Metadata (IIS Config)
function Backup-IISMetadata
{
if (!(Test-Path $env:windir\system32\inetsrv\backup))
{
Create-Folder $env:windir\system32\inetsrv\backup
}

$iisBackupFile = "IISMetaData-" + $(Get-Date -Format ddMMyyyyHHmmss)
       
try
{
<#
A directory with the backup name is created under the %windir%\system32\inetsrv\backup directory AppCmd copies the
current configuration files into the backup directory, including applicationHost.config (IIS global configuration)
administration.config (Admin Tool's configuration), metabase.xml and mbschema.xml.
#>

& $env:windir\system32\inetsrv\appcmd.exe add backup $iisBackupFile | Out-Null
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Warning: IIS MetaData (IIS Config) backup job failed. Reason: $errText"
$GLOBAL:hasErrors = $true
}

$zipSrc = "$env:windir\system32\inetsrv\backup\$iisBackupFile"
$zipTgt = Join-Path $iisBackupDirectory $iisBackupFile".zip"

try
{
Get-Item -Path $zipSrc -ErrorAction Stop | Out-Zip $zipTgt -ErrorAction Stop
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Error: IIS MetaData (IIS Config)backup job failed. Reason: $errText"
$GLOBAL:hasErrors = $true
}

Write-Log "Info: Backup and compression of $zipSrc complete."
}
#EndRegion

#Region Execute export of Solutions
function Export-Solutions
{
Write-Log "Info: Exporting solutions."

try
{
foreach ($solution in Get-SPSolution -ErrorAction Stop)
{
$solutionName = $Solution.SolutionFile.Name
$solution.SolutionFile.SaveAs("$solutionsDirectory\$solutionName")

if ($?)
{
Write-Log "Info: Export of $solutionName completed successfully."
}
}
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Error: Export of solution(s) failed. Reason: $errText"
$GLOBAL:hasErrors = $true
}

Write-Log "Info: Export of solution(s) completed successfully."
}
#EndRegion

#Region Execute backup and compression of SharePoint Virtual Directories
function Backup-IISVirtualDirectories
{
Write-Log "Info: Backing up & compressing SharePoint virtual directories."

try
{
$urls = $farm.AlternateUrlCollections

foreach ($url in $urls)
{
$webApplication = Get-SPWebApplication -Identity $url.IncomingUrl
$webApplicationName = $webApplication.DisplayName
$Zone = $url.UrlZone # Get the default zone for the web application

$iisBackupFile = "IISVirtualDirectory-$webApplicationName-$Zone-" + $(Get-Date -Format ddMMyyyyHHmmss) + ".zip"

<#
When creating a web application, one of the fields allows you to set the path to the virtual directory. The default path
is ususally IIS root. The default being: <Drive:>\inetpub\wwwroot\wss\VirtualDirectories\80 (Normally C:\ unless you have
moved IIS from default to another drive which is a manual process).

The following line allows you to get the IIS Settings for the Zone which includes PATH which will ensure the backup of the
SharePoint virtual directories if the location is set to a different location than the default.
#>

$iisSettings = $webApplication.iisSettings[$Zone] # Get the IIS settings for the zone

if (($iisSettings -eq $null) -or ($iisSettings.Path -eq $null)){
continue;
}

$iisPath = $iisSettings.Path.ToString()  # Get path from IIS settings

# Zip the SharePoint virtual directory
$zipSrc = $iisPath
$zipTgt = Join-Path $iisBackupDirectory $iisBackupFile

if (!(Test-Path $zipTgt))
{
Get-Item -Path $zipSrc -ErrorAction Stop | Out-Zip $zipTgt -ErrorAction Stop
Write-Log "Info: Backup and compression of $zipSrc for the $Zone zone of $webApplicationName web application is complete."
}
}
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Warning: An error occurred whilst backing up the Virtual directories. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Backup individual web.config files
function Backup-WebConfig
{
Write-Log "Info: Backing up and compressing individual web.config files."

<#
Unless ALL your web.config modifications are made via Central Admin, or the API (SPWebConfigModification)  web.config changes
or those made by third party applications or solutions are not captured during a backup and are not available for restore.

  Example: Changes to web.config file that have been made to support claims-based application that uses forms-based authentication
  are not included in backups because those changes are made manually.
#>

try
{
$urls = $farm.AlternateUrlCollections

foreach ($url in $urls)
{

$webApplication = Get-SPWebApplication -Identity $url.IncomingUrl
$webApplicationName = $webApplication.DisplayName
$Zone = $url.UrlZone # Get the default zone for the web application

$webConfigBackupFile = "WebConfig-$webApplicationName-$Zone-" + $(Get-Date -Format ddMMyyyyHHmmss) + ".zip"

$iisSettings = $webApplication.iisSettings[$Zone] # Get the IIS settings for the zone

if (($iisSettings -eq $null) -or ($iisSettings.Path -eq $null)){
continue;
}

$webConfigPath = Join-Path $iisSettings.Path.ToString() "\web.config" # Get path from IIS settings

# Zip the web.config file
$zipSrc = $webConfigPath
$zipTgt = Join-Path $iisBackupDirectory $webConfigBackupFile

if (!(Test-Path $zipTgt))
{
Get-Item -Path $zipSrc -ErrorAction Stop | Out-Zip $zipTgt -ErrorAction Stop
Write-Log "Info: Backup and compression of $zipSrc for the $Zone zone of $webApplicationName web application is complete."
}
}
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Warning: An error occurred. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Backup farm's ULS log storage location
function Backup-ULSLogs
{
    # Merge log files
    try
{
Write-Log "Info: Performing merge of ULS log(s) from all servers in farm. Current settings merge only the last 60 minutes logs (with no filtering). More Info: http://technet.microsoft.com/en-us/library/ff607721.asp"
Merge-SPLogFile -path $logsBackup\MergedLog-$(Get-Date -Format ddMMyyyyHHmmss).log -ErrorAction Stop
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Warning: Merge of ULS log(s) failed. Reason: $errText"
$GLOBAL:hasErrors = $true
}

Write-Log "Info: Merge of ULS log(s) successfully completed."
}
#EndRegion

<#
#Region Backup farm's ULS logs
function Backup-ULSLogs
{
$endTime = Get-Date
$startTime = $endTime.AddDays(-1)
   
# Merge log files
    try
{
Write-Log "Info: Performing merge of ULS log(s) from all servers on farm from $startTime until $endTime (previous 24 Hours). More Info: http://technet.microsoft.com/en-us/library/ff607721.asp"
Merge-SPLogFile –Path $logsBackup\MergedLog-$(Get-Date -Format ddMMyyyyHHmmss).log –StartTime $startTime –EndTime $endTime –Overwrite -ErrorAction Stop
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Warning: Merge of ULS log(s) failed. Reason: $errText"
$GLOBAL:hasErrors = $true
}

Write-Log "Info: Merge of ULS log(s) successfully completed."
}
#EndRegion
#>

#Region Check that My Site Host exists and is accessible.
function Check-MySiteHost
{
$MySiteHostPresent = 0

Write-Log "Info: Checking that My Site Host exists and is accessible."

<#

Errors that commonly occur when determining the My Site Host location are:

1. Error: Exception calling ".ctor" with "1" argument(s): "Object reference not set to an instance of an object."
           Possible Cause: Account executing script does not have permissions on the User profile service application.

2. Error: Exception calling ".ctor" with "1" argument(s): "No User Profile Application available to service the request. Contact your farm administrator."
           Possible Cause: User profile service application is not provisioned or is stopped.

3. Error: Cannot find type [Microsoft.Office.Server.UserProfiles.UserProfileManager]: make sure the assembly containing this type is loaded.
           Possible Cause: Returned when script is executed on SharePoint 2010 Foundation which does not have My Sites and User profile service application services.

#>

try
{
# Loop through all web applications (specify filter criteria here if you want to filter web applications out)
foreach ($webApplication in Get-SPWebApplication -IncludeCentralAdministration:$false -ErrorAction Stop)
{
# Strip trailing slash from Web Application URL
$GLOBAL:currentWebApplication = $webApplication.Url.Trim("/")

if(Get-MySiteHost -eq $true)
   {
$MySiteHostPresent += 1
}
}

if($MySiteHostPresent -ge 1)
   {
      if($MySiteHost.Url.length -eq 0)
  {
Write-Log "Warning: An error occurred whilst determining the My Site Host. Reason: My Site Host is Null/Empty."
}
}
else
{
Write-Log "Warning: An error occurred whilst determining the My Site Host. Reason: $MySiteErrText"
}
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Warning: An error occurred whilst checking for the My Site Host. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Get MySite Host URL for inclusion/exclusion of MySites as part of Site(s) backup.
function Get-MySiteHost
{
$webApplication = Get-SPWebApplication -Identity $currentWebApplication -ErrorAction Stop

try
{
## Get context of the service application
$svcContext = Get-SPServiceContext($webApplication.Url)
$profileManager = New-Object Microsoft.Office.Server.UserProfiles.UserProfileManager($svcContext) -ErrorAction Stop
$GLOBAL:MySiteHostUrl = $profileManager.MySiteHostUrl

$GLOBAL:MySiteHost = Get-SPSite -Identity $MySiteHostUrl -ErrorAction Stop

   # If MySiteHost is the same ID as the Web Application being queried then return true
if($MySiteHost.WebApplication.Id -eq $webApplication.Id)
{
return $true
}
else
{
return $false
}
}
catch
{
$GLOBAL:MySiteErrText = $error[0].Exception.Message
$GLOBAL:hasErrors = $true

return $false
}
}
#EndRegion

#Region Loads the spbrtoc.xml (Definition: SharePoint Backup/Restore Table of Contents XML)
function Load-BackupTocXML
{
# Set location of spbrtoc.xml
$spbrtoc = $farmBackupDirectory + "spbrtoc.xml"

if (!(Test-Path $spbrtoc))
{
Write-Log "Warning: spbrtoc.xml file not found. NOTE: This is normal if this the first time the job has run."
return
}
else
{
$toc = [xml](Get-Content $spbrtoc)
Write-Log "Info: spbrtoc.xml file found. Action: Loading XML."
}

foreach($item in $toc.SPBackupRestoreHistory.SPHistoryObject)
{
Clean-BackupHistory ([ref]$item)
}

# Save Table of Contents
$toc.Save($spbrtoc)
}
#EndRegion

#Region Convert DateTime to same format as System Locale (required when farm local is different to system locale)
function Convert-DateTime
{
param ([string]$date)

# Get Farm Locale (Language/Regional Settings)
$Locale = [Microsoft.SharePoint.SPRegionalSettings]::GlobalServerLanguage
$Locale = [System.Globalization.CultureInfo]::GetCultureInfo($Locale.LCID)

    # Get CultureInfo for parsing date
    $Culture = New-Object System.Globalization.CultureInfo($Locale)
    $dateOut = [DateTime]::Parse($date, $Culture)
   
return $dateOut

}
#EndRegion

#Region Deletes old/previous backups based on $days value. $days value stipulates no of days backups to be retained.
function Clean-BackupHistory
{
    Param ([ref]$itemRef)

Write-Log "Info: Checking for backups older than $days day(s)."
   
    $item = $itemRef.Value
   
$directoryName = $item.SPDirectoryName
$startTime = Convert-DateTime $item.SPStartTime
   
if ($directoryName -ne $null)
{
if ($startTime -lt (Convert-DateTime (Get-Date).AddDays(-$days)))
{
try
{
Write-Log "Info: SharePoint farm backup $directoryName older than $days day(s) found. Action: $directoryName deleted."
Remove-Item $item.SPBackupDirectory -Recurse -ErrorAction Stop
$item.ParentNode.RemoveChild($item)
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Error: Unable to delete backup entry(ies). Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
else
{
Write-Log "Info: SharePoint farm backup $directoryName not older than $days day(s). Action: Nothing deleted."
}
}
else
{
Write-Log "Warning: No existing farm backup folder found. NOTE: This is normal if this the first time the job has run."
}
}
#EndRegion

#Region Get a summary of the most recent backup (including Warnings & Errors)
function Get-BackupHistory
{
try
    {
        $backup = @(Get-SPBackupHistory -Directory $farmBackupDirectory -ShowBackup)
       
        $latestBackupStartTime = $backup[0].StartTime
    #$GLOBAL:latestBackupStartTime = $latestBackupStartTime.ToString("ddMMyyyy")
$GLOBAL:latestBackupErrorCount = $backup[0].ErrorCount
    $GLOBAL:latestBackupWarningCount = $backup[0].WarningCount
$GLOBAL:latestBackupFailed = $backup[0].IsFailure
$GLOBAL:latestBackupFailureMessage = $backup[0].FailureMessage
$GLOBAL:latestBackupDirectory = $backup[0].Directory # Full Path
        $GLOBAL:latestBackupDirectoryName = $backup[0].DirectoryName # Short Name
}
    catch
    {
        $errText = $error[0].Exception.Message
Write-Log "Error: There was a problem obtaining the farm backup summary. Reason: $errText"
$GLOBAL:hasErrors = $true
    }      
}
#EndRegion

#Region Sends the spbackup.log file from the recently completed backup
function Email-BackupLog
{
# Pause script to ensure compression completes. Value may need increasing depending on size of GAC and 14Hive
Start-Sleep -Seconds 150

$backupLogfile = $latestBackupDirectory + "spbackup.log"

if (!(Test-Path $backupLogfile))
{
Write-Log "Error: spbackup.log file not found. Check that SharePoint backup log [spbackup.log] exists. Nothing sent to nominated recipient(s)."

if ($latestBackupErrorCount -ne '0')
{
Write-Log "Info: The site/farm backup job completed with error(s). Check SharePoint backup log [spbackup.log] for further details."
Log-WarningEvent "SharePoint site/farm backup job completed with error(s). Check SharePoint backup log [spbackup.log] for further details."
$GLOBAL:hasErrors = $true
}
else
{
Write-Log "Info: All tasks for site/farm backup job completed. Review the SharePoint backup log [spbackup.log] for a detailed summary."
Log-InformationEvent "SharePoint Farm Backup job completed successfully at: $endDTM`n`nReview the SharePoint backup log [spbackup.log] for a detailed summary."
}
}
else
{  
if ($sendEmail -eq $true)
{
Write-Log "Info: E-mailing farm backup log $backupLogfile to nominated recipient(s)."

if (($hasErrors -eq $false) -and ($latestBackupErrorCount -eq '0') -and ($latestBackupWarningCount -eq '0'))
{
$jobStatus = "Completed Successfully."
}
elseif (($hasErrors -eq $false) -and ($latestBackupErrorCount -gt '0') -and ($latestBackupWarningCount -gt '0'))
{
$jobStatus = "Completed with Error(s) and Warning(s)."
}
elseif (($hasErrors -eq $false) -and ($latestBackupErrorCount -gt '0') -and ($latestBackupWarningCount -eq '0'))
{
$jobStatus = "Completed with Error(s)."
}
            elseif (($hasErrors -eq $false) -and ($latestBackupErrorCount -eq '0') -and ($latestBackupWarningCount -gt '0'))
{
$jobStatus = "Completed with Warning(s)."
}
elseif (($hasErrors -eq $true) -and ($latestBackupErrorCount -eq '0') -and ($latestBackupWarningCount -eq '0'))
            {
$jobStatus = "Completed with Script Error(s). Review $jobReport."
}
           
$jobSummary = "SharePoint site/farm backup job has completed.`n`nResult: " + $jobStatus + "`nTime: " + (Get-Date -Format "dd-MMM-yyyy HH:mm:ss") + "`nBackup Method: " + $backupMethod + "`nWarnings: " + $latestBackupWarningCount + "`nErrors: " + $latestBackupErrorCount + "`nRemarks: Review the attached SharePoint backup log [spbackup.log] for a detailed summary."
$fileAttachment = $backupLogfile

Send-Email $jobSummary $fileAttachment

}
}

# Get End Time
$endDTM = (Get-Date)

  $duration = [math]::round($(($endDTM-$startDTM).totalminutes),2)

# Log duration and time of job completion
Write-Log "Result: SharePoint site/farm backup job completed in: $duration minutes."
Log-InformationEvent "SharePoint site/farm backup job completed at: $endDTM. Status: $jobStatus"  
}
#EndRegion

#Region Add file(s) to zip archive
# Acknowledgements: Function provided by Brian Lalancette of Navantis
function Out-Zip
{
  Param([string]$path)
 
  try{
 $files = $input

 if (-not $path.EndsWith('.zip')) {$path += '.zip'}

 if (!(Test-Path $path)) {
   Set-Content $path ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
 }

 $zipFile = (New-Object -com shell.application).NameSpace($path)
 $files | ForEach {$zipFile.CopyHere($_.fullname)}
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Warning: There was a problem generating zip archive. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Halts script in the event of an error and logs cause to backup job report
function Halt-OnError
{
param ([string]$errorMessage)

Write-Log "Error: $errorMessage"

$jobStatus = "Exited prematurely with error(s)."
$jobSummary = "SharePoint site/farm backup job exited prematurely with error(s). Review backup job report [" + $(Get-Date -format dd-MMM-yyyy) + "-backupjobreport.log] for further details."

if ($sendEmail -eq $true) {

Send-Email $jobStatus $jobSummary
}

Log-ErrorEvent  $jobSummary
Write-Log "Result: SharePoint site/farm backup job exited prematurely with error(s)."

Exit
}
#EndRegion

#Region Generate Information/Warning/Error events for writing to Application event log
function Log-InformationEvent
{
param ([string]$eventMessage)

$entryType = "Information"
$eventMsg = $eventMessage
$eventID = "1001"

Write-Event $entryType $eventMsg $eventID
}

function Log-WarningEvent
{
param ([string]$eventMessage)

$entryType = "Warning"
$eventMsg = $eventMessage
$eventID = "1001"

Write-Event $entryType $eventMsg $eventID
}

function Log-ErrorEvent
{
param ([string]$eventMessage)

$entryType = "Error"
$eventMsg = $eventMessage
$eventID = "1001"

Write-Event $entryType $eventMsg $eventID
}
#EndRegion

#Region Write Information/Warning/Error events to Application event log
function Write-Event
{
param ( [String]$entryType, [String]$eventMsg, [String]$eventID)

# Create the Event Log Source type if it doesn't exist.
if (!(Test-Path HKLM:\SYSTEM\CurrentControlSet\Services\Eventlog\Application\$EventSourceType)) {
New-EventLog -logname Application -ComputerName $Computer -source $EventSourceType -ErrorAction SilentlyContinue
}

# Create the Int16 Structure
[System.Int16] $i = 0

# Write the event to the servers Application log. As stated above default EventID is 1001 but can be modified to suit your needs
Write-EventLog -category $i -LogName Application -Source $EventSourceType -EventID $eventID -Message $eventMsg -EntryType $entryType  
}
#EndRegion

#Region Send e-mail to nominated recipient(s)
function Send-Email
{
try
{
$msg = new-object Net.Mail.MailMessage
$msg.From = $emailFrom

if ($emailTo -ne "")
{
$msg.To.Add($emailTo)
}
else
{
Write-Log "Warning: E-mail was not sent. Reason: There is no nominated recipient."
break
}

if ($emailCC -ne "")
{
$msg.CC.Add($emailCC)
}

$msg.Subject = "SharePoint site/farm backup of $env farm completed."
$msg.Body = $jobSummary

#Attach file, if applicable
if ($fileAttachment -ne $null)
{
$fileAttachment = New-Object Net.Mail.Attachment($fileAttachment)
$msg.Attachments.Add($fileAttachment)
}

$smtp = new-object Net.Mail.SmtpClient($smtpServer)
$smtp.Send($msg)
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Error: Sending mail to one or more recipients failed. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Create folder/directory
function Create-Folder
{
param ([String]$folder)

try
{
New-Item $folder -type directory -ErrorAction SilentlyContinue
Write-Log "Info: $folder not present. Action: Folder $folder created."
}
catch
{
$errText = $error[0].Exception.Message
Halt-OnError "There was a problem creating the folder $folder. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Writes progress of script and/or job completion status to log file
function Write-Log
{
param ([string]$info)

$now = Get-Date -Format "dd-MMM-yyyy HH:mm:ss"
$now + "`t$info" >> $jobReport
}
#EndRegion

#Region Check enough storage available for SharePoint farm backup
function Check-BackupStorage
{
Get-DriveLetter # Determine the next available drive letter

if ($driveLetter -eq $null)
    {
Write-Log "Warning: A problem occurred whilst attempting to check storage available. Unable to assign a drive letter. Action: Backup will attempt to continue but may fail if insufficient storage available."
$GLOBAL:hasErrors = $true
}
else
{
Get-BackupStorageAvailable # Checks for the available storage on drive
if($backupSites -eq $true)
        {
            Get-SiteStorageRequired  # Checks for storage used by all site collections
        }
Get-ContentStorageRequired # Checks for storage required for farm backup
Get-BackupStorageUsed # Checks for storage currently used
Has-FreeSpace # Condition to check storage available is sufficient for impending backup.
}
}
#EndRegion

#Region Checks to determine whether storage available is sufficient, if insufficient storage, attempts to free up storage.
function Has-FreeSpace
{
    $contentStorageUsed = $GLOBAL:contentStorageUsed + $GLOBAL:siteStorageUsed
     
    try
{
if ($contentStorageUsed -gt $GLOBAL:backupStorageAvailable)
{
Write-Log "Info: There is not enough storage available to carry out a FULL farm backup. Action: Attempting to clean up old backups (based on retention stipulated in params.xml)and free up additional storage."
Clean-Backups # Remove old backups (GAC/IIS/14Hive/Logs)
Get-BackupHistory # Get the details of the most recent backup
Load-BackupTocXML # Remove old farm backups
Get-BackupStorageAvailable # Checks for the available storage on drive

if($backupSites -eq $true)
            {
                Get-SiteStorageRequired  # Checks for storage used by all site collections
            }

Get-ContentStorageRequired # Checks for storage required for farm backup
}

if (($contentStorageUsed -gt $GLOBAL:backupStorageAvailable) -and ($GLOBAL:backupMethod -ne "DIFFERENTIAL"))
{

Dismount-NetworkDrive $driveLetter
Halt-OnError "There is not enough storage available to carry out a FULL farm backup. Action: Ensure there is sufficient storage available and re-launch backup."
}
else
{
if($GLOBAL:backupStorageFreePercent -lt $minStoragePercentFree)
{
Write-Log "Warning: Current storage available for DIFFERENTIAL backup is less than $minStoragePercentFree%. Actual storage available is: $GLOBAL:backupStorageAvailable GB ($backupStorageFreePercent%). Backup will continue but may fail at a later stage if insufficient storage is unavailable."
}
}
       
Dismount-NetworkDrive $driveLetter
}
catch
{
$errText = $error[0].Exception.Message
Write-Log "Error: An error occurred whilst determining whether sufficient storage available. $errText"
$GLOBAL:hasErrors = $true
}
}
#EndRegion

#Region Execute individual backup jobs (Farm/Site/IIS/GAC/14Hive/ULS Logs)
function Backup-IIS
{
if ($backupWebConfigOnly -eq $true)
{
Write-Log "Warning: You have opted to backup web.config only. SharePoint virtual directories & IIS Metadata (IIS Config) will NOT be backed up."
Backup-WebConfig
}
elseif ($backupIIS -eq $true)
{
Backup-WebConfig
Backup-IISMetadata
Backup-IISVirtualDirectories
}
}

function Backup-RelatedTargets
{
if ($backupULSLogs -eq $true)
{
Backup-ULSLogs
}
if ($backupGAC -eq $true)
{
Backup-GAC
}
if ($backup14hive -eq $true)
{
Backup-14hive
}
if ($exportSolutions -eq $true)
{
Export-Solutions
}
}

function Start-FarmBackup
{
Backup-RelatedTargets # Backup related targets including: GAC/14Hive/ULSLogs/Solutions
Backup-IIS # Backup IIS components including: SharePoint Virtual Directories, Metadata (IIS Config) & web.config

if ($backupSites -eq $true)
{

<#
During the execution of the Backup-SPSite cmdlet the site is locked unless the -NoSiteLock parameter is set. Locking the site during
backup means the backup operation reads/writes are prevented and users are unable to access the site. An issue was identified where
in the event of a site backup failure the site could potentially be left in a 'locked' state.

To gaurd again this the script includes the option to use the -UseSqlSnapshot parameter which allows the user to continue normal use
of the site collection whilst it is being backed up. Indeed, Microsoft recommend using the -UseSqlSnapshot parameter.

However, the usage of the -UseSqlSnapshot parameter has a number of caveats. Firstly, the version of SQL required is SQL Server 2008
(Enterprise Edition) with Service Pack 1 and Cumulative Update 2 or higher. Secondly, Microsoft have highlighted an issue where any
farm not having had applied SP1 would incur the following error:

"Operation is not valid due to the current state of the object."

This does not mean that site backup has failed, quite the opposite in fact, the site backup does succeeded. However, until the application
of SP1 this error will continue to occur. Of course if you are looping through all sites and use a try catch block the script will exit
in the catch block. The Backup-Sites method is written to cater for this and will allow the backup operation for all sites to complete.
If the "Operation is not valid due to the current state of the object." occurs it is logged once for information purposes only and perhaps
to act as a gentle prompt to patch the farm as soon as operationally possible.

Finally, Microsoft also state that if the RBS provider that you are using does not support snapshots, you cannot use snapshots for content
deployment or backup. For example, the SQL FILESTREAM provider does not support snapshots.

More Info: http://technet.microsoft.com/en-us/library/ee748617.aspx

In summary:

Whilst Microsoft recommend the use of the -UseSqlSnapshot parameter as the preferred option when using the Backup-SPSite cmdlet there are
three requirements that need to be met in order to allow usage. These are:

1. Farm must be running SQL Server 2008 (Enterprise Edition) with Service Pack 1 and Cumulative Update 2 or higher.
2. To prevent the error "Operation is not valid due to the current state of the object." Service Pack 1 must be applied to the farm.
3. The SQL FILESTREAM provider can not be used in conjunction with the -UseSqlSnapshot parameter as the SQL FILESTREAM provider does not
  support snapshots.
 
The script will attempt to determine that all the criteria for using the -UseSqlSnapshot parameter are met and in the event they are not then
will default to backing up the sites without the -UseSqlSnapshot parameter but will ensure even in the event of a site backup failure the site
is set back to its orginal lock state before the backup operation took place.
#>

try
{
Backup-Sites # Backup sites in farm
}
catch
   {
$errText = $error[0].Exception.Message
Write-Log "Error: SharePoint sites backup failed. Reason: $errText"
$GLOBAL:hasErrors = $true
}
}
else
{
Write-Log "Info: Backup sites set to: $backupSites. Skipping site collection backup."
}

Backup-Farm # Backup SharePoint farm
Get-BackupHistory # Get the details of the most recent backup
Load-BackupTocXML # Load spbrtoc.xml
Get-BackupHistory # Get details of Errors/Warnings
Email-BackupLog # Email latest spbackup.log to nominated recipients.
}
#EndRegion

#Region Executes IIS Reset following completion of backup tasks. Mandatory if hosting Central Administration on same server as the User Profile Synchronization
function Reset-Services
{
   
 # Connect to the farm
 $farm = Get-SPFarm
   
    foreach ($server in $farm.Servers)
    {
      $serverName = $server.Name
      $serverRole = $server.Role
       
        if ($server.Role -ne "Invalid")
        {
            IISReset $serverName
        }
    }
}
#EndRegion

#Region MAIN - Preparatory steps for SharePoint farm backup

try
{
Load-ParamsXML # Load params.xml
Get-Folders # Check to see if backup share and backup folders are present
Load-Snapin # Load PowerShell Snap-In
Check-BackupStorage # Check storage available for backup
Get-Farm  # Connect to SharePoint farm
Get-Role  # Determine whether server is a SharePoint server
Get-FarmDbServer # Determine farm database server
Is-SnapshotCompatible # Determine whether -UseSqlSnapshot can be used with this SQL configuration
Clean-Backups # Remove old backups (GAC/IIS/14Hive/Logs)
Start-FarmBackup # Launch SharePoint farm backup tasks
#Reset-Services # Conclude backup with IIS Reset. See: http://www.darrenmarsden.com/post/2012/04/04/SharePoint-Backup-breaks-User-Profile-Service.aspx
}
catch
{
$errText = $error[0].Exception.Message
Halt-OnError "An error occured during execution of backup script. Reason: $errText"
}
#EndRegion

No comments:

SSL Error - The connection for this site is not secure

 After cloning a git repo of dot net framework website and trying to run it all I could see was this error Turns out the fix was to simply e...