Tuesday, March 17, 2009

SharePoint Farm Backup spbackup (codeplex)

Jesper M. Christensen has provided a simple yet affective tool for backing up a SharePoint farm, http://www.codeplex.com/spbackup.

This tool has been developed in PowerShell script and is quite configurable with the following options:
catastrophicbackup: Set this value to 1 to perform a complete farm backup and 0 to skip this backuptype
catastrophicmethod: Set this to full or differential
sitecollectionbackup: Set this value to 1 to perform an individual backup of a site collection and subsites and 0 to skip this backuptype
sitecollectionurl: Set this to the site collection url you want to perform an individual backup of
hivebackup: Set this value to 1 to perform a backup of your 12-hive
hive: Path to your 12-hive (usually located at C:\Program Files\Common Files\Microsoft Shared\web server extensions\12
iisbackup: Set this value to 1 to perform a backup of the IIS Metadata and 0 to skip this backuptype
iisencryptopassword: Type a password here to make the IIS6 Metadata backup restorable on other servers
smtpserver: Type a smtp server to send mail through
fromemail: E-mail address to send from
backupdestination: Type a valid path for the backupfiles
backupdestinationmaxkeepdays: If set to 0 no files are deleted. Set another number to delete files older .zip/.backup-files. Full/differential backup files are NOT deleted (from spbackup version 1.2)


Wes Macdonald has provided some useful tips to extend this PwerShell script to achieve additional backups to comply closer to the Disaster and Recovery Guide from Microsoft. This includes Alternate Access Mappings and the GAC.
See http://spbackup.codeplex.com/Thread/View.aspx?ThreadId=48260


My implantation of combinding Jaspers spback with Wes's suggestions are as follows:


#Get XML configuration file parameter
param (
[string]$Configurationfile = "c:\SPBackup\SPBackup.xml"
)

function Logging {
$logmsg = $args[0]
Write-Host $logmsg
$jobreport+[Environment]::NewLine+[System.DateTime]::Now
$logmsg
}

function sendemail
{
# ---
# --- Report to e-mail if email and smtpserver is defined ---
# ---
If ($fromemail)
{
If ($toemail)
{
If ($smtpserver)
{
$jobreport = logging("Ending with job status """+$jobstatus+""" for site id "+$id)
$jobreport = logging("Sending job report on email")
$subject = $jobstatus +": SPBackup has completed on "+$env:computername+" with "+[System.DateTime]::Now
$msg = new-object System.Net.Mail.MailMessage $fromemail, $toemail, $subject, $jobreport
$client = new-object System.Net.Mail.SmtpClient $smtpserver
$client.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
$client.Send($msg)
}
else
{
$jobreport = logging("Skipping Report to e-mail because of missing smtpserver")
}
}
else
{
$jobreport = logging("Skipping Report to e-mail because of missing toemail address")
}
}
else
{
$jobreport = logging("Skipping Report to e-mail because of missing fromemail address")
}
}

function out-zip {
$path = $args[0]
$files = $input

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

if (-not (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) }
}

If (Test-Path $Configurationfile) {
$cfg = [xml] (get-content $Configurationfile)
$jobreport = logging("Started backup with configuration file "+$Configurationfile)
}
Else
{
$jobstatus="Error"
$jobreport = logging( "Error: Configuration XML file not found!")
sendemail
exit
}
$QueryOS = Gwmi Win32_OperatingSystem -Comp localhost
$QueryOS = $QueryOS.Caption

If ($QueryOS.contains("2008") -or $QueryOS.contains("Vista"))
{
$jobreport = logging("Windows Server 2008 or Vista detected")
}

If ($QueryOS.contains("2003"))
{
$jobreport = logging("Windows Server 2003 detected")
}

#Go through each site-configuration in the file
$nodelist = $cfg.selectnodes("/confSPBackup/site")
foreach ($item in $nodelist) {
#Read this site parameters
$jobstatus="Success"
$aambackup = $item.alternateaccessmapping
$id = $item.getAttribute("id")
$catastrophicbackup = $item.catastrophicbackup
$catastrophicmethod = $item.catastrophicmethod
$sitecollectionbackup = $item.sitecollectionbackup
$sitecollectionurl = $item.sitecollectionurl
$hivebackup = $item.hivebackup
$hive = $item.hive
$iisbackup = $item.iisbackup
$iisencryptopassword = $item.iisencryptopassword
$smtpserver = $item.smtpserver
$fromemail = $item.fromemail
$toemail = $item.toemail
$backupdestination = $item.backupdestination
$backupdestinationmaxkeepdays = $item.backupdestinationmaxkeepdays
$backupfilename = $sitecollectionurl.replace("http://","")
$backupfilename = $backupfilename.replace("/","_")
$guid = "-" + [Guid]::NewGuid().ToString()

#Print the configuration parameter-details
$jobreport = logging("Starting session"+$guid)
$item

# ---
# --- Delete old files from backupdestination if backupdestinationmaxkeepdays<>0 ---
# ---
If ([string]::Compare($backupdestinationmaxkeepdays, "0", $True))
{
#Perform deletion if backup destination path is valid
if(test-path $backupdestination)
{
& dir $backupdestination ? {$_.CreationTime -lt (get-date).AddDays(-$backupdestinationmaxkeepdays) -and $_.name.EndsWith('.zip')} del -force
& dir $backupdestination ? {$_.CreationTime -lt (get-date).AddDays(-$backupdestinationmaxkeepdays) -and $_.name.EndsWith('.backup')} del -force
[DateTime]::Now.ToString() + ": Files on backup destination "+$backupdestination+" older than "+$backupdestinationmaxkeepdays+" days deleted!"
}
else
{
$jobstatus="Error"
$jobreport = logging("Error: The backupdestination Path doesn't exists for site id "+$id)
}
}
else
{
$jobreport = logging("Skipping deletion of old files on backupdestination")
}


$gacbackup = $item.gacbackup
$gac = $env:windir+"\assembly\gac_msil\"

# ---
# --- Do a INETPUB backup if inetpubbackup=1 ---
# ---



# ---
# --- Do a GAC backup if gacbackup=1 ---
# ---

#Global Assembly Cache
#Added element in the xml configuration file and modified the portion of the script which reads the XML to include

$gacbackup = $item.gacbackup
$gac = $env:windir+"\assembly\gac_msil\"

If ([string]::Compare($gacbackup, "0", $True))
{
$jobreport = logging("Starting backup of GAC to "+$backupdestination+"\"+$backupfilename+"-gac"+$guid+".zip")
if(test-path $backupdestination)
{
if(test-path $gac)
{
Write-Zip -Path $gac -IncludeEmptyDirectories -OutputPath $backupdestination\$backupfilename"-gac"$guid".zip" > $null
[DateTime]::Now.ToString() + ": GAC backup Done! File name is $backupdestination\$backupfilename-gac$guid.zip" >> "$backupdestination\log.txt"
}
else
{
$jobstatus = "Error"
$jobreport = logging("Error: The GAC Path doesn't exist for site id "+$id)
}
}
else
{
$jobstatus = "Error"
$jobreport = logging("Error: The backupdestination Path doesn't exists for site id "+$id)
}
}
else
{
$jobreport = logging("Skipping backup of GAC "+$gac)
}

Start-Sleep 10

# ---
# --- Do a hive backup if hivebackup=1 ---
# ---
If ([string]::Compare($hivebackup, "0", $True))
{
$jobreport = logging("Starting backup of hive "+$hive+" to "+$backupdestination+"\"+$backupfilename+"-12hive"+$guid+".zip")
if(test-path $backupdestination)
{
if(test-path $hive)
{
gi $hive out-zip $backupdestination"\"$backupfilename"-12hive"$guid".zip" $_
[DateTime]::Now.ToString() + ": Hive backup Done! File name is $backupdestination\$backupfilename-12hive$guid.zip" >> "$backupdestination\log.txt"
}
else
{
$jobstatus="Error"
$jobreport = logging("Error: The 12hive Path doesn't exists for site id "+$id)
}
}
else
{
$jobstatus="Error"
$jobreport = logging("Error: The backupdestination Path doesn't exists for site id "+$id)
}
}
else
{
$jobreport = logging("Skipping backup of hive "+$hive)
}
Start-sleep 30





# ---
# --- Do a IIS Metadata backup if iisbackup=1 ---
# ---
If ([string]::Compare($iisbackup, "0", $True))
{
$jobreport = logging("Starting backup of IIS Metadata to "+$backupdestination+"\"+$backupfilename+"-IISMetadata"+$guid+".zip")
if(test-path $backupdestination)
{
If ($QueryOS.contains("2003"))
{
if(test-path $env:windir\system32\inetsrv\metaback)
{
#Check if iisuser and iispassword are entered if not run the iisback.vbs with local machine encryption
#With local machine encryption the IIS Metadata can only be restored on this particullar IIS instance/Server
If ($iisencryptopassword)
{
$jobreport = logging("The IIS Metadata is done using encryption password specified")
& cscript.exe $env:windir\system32\iisback.vbs /backup /e $iisencryptopassword /b $env:computername /overwrite
}
else
{
$jobreport = logging("The IIS Metadata is done using machine credentials (restore on same IIS instance/server only)")
& cscript.exe $env:windir\system32\iisback.vbs /backup /b $env:computername /overwrite
}
ls $env:windir\system32\inetsrv\metaback\$env:computername.* out-zip $backupdestination"\"$backupfilename"-IISMetadata"$guid".zip" $_
[DateTime]::Now.ToString() + ": IIS Metadata backup Done! File name is $backupdestination\$backupfilename-IISMetadata$guid.zip" >> "$backupdestination\log.txt"
}
else
{
$jobstatus="Error"
$jobreport = logging("Error: The IIS Metadata Path doesn't exists for site id "+$id)
}
}
If ($QueryOS.contains("2008"))
{
& $env:windir\system32\inetsrv\appcmd.exe add backup
gi $env:windir\system32\inetsrv\backup out-zip $backupdestination"\"$backupfilename"-IISMetadata"$guid".zip" $_
[DateTime]::Now.ToString() + ": IIS Metadata backup Done! File name is $backupdestination\$backupfilename-IISMetadata$guid.zip" >> "$backupdestination\log.txt"

}
}
else
{
$jobstatus="Error"
$jobreport = logging("Error: The backupdestination Path doesn't exists for site id "+$id)
}
}
else
{
$jobreport = logging("Skipping backup of IIS Metadata ")
}
Start-sleep 10

# ---

# --- Do a Alternate Access Mappings backup if aambackup=1 ---
# ---
If ([string]::Compare($aambackup, "0", $True))
{
$jobreport = logging("Starting backup of Alternate Access Mappings to "+$backupdestination+"\"+$backupfilename+"-aam"+$guid+".xml")
if(test-path $backupdestination) {
& $hive\BIN\stsadm.exe -o enumalternatedomains >> $backupdestination"\"$backupfilename"-aam"$guid.xml
[DateTime]::Now.ToString() + ": Alternate Access Mappings backup Done! File name is $backupdestination\$backupfilename-aam$guid.xml" >> "$backupdestination\log.txt"
}
else
{
$jobstatus = "Error"
$jobreport = logging("Error: The backupdestination Path doesn't exist for site id "+$id)
}
}
else
{
$jobreport = logging("Skipping backup of Alernate Access Mappings.")
}



# ---
# --- Do a site collection crawl and backup if sitecollectionbackup=1 ---
# ---
If ([string]::Compare($sitecollectionbackup, "0", $True))
{
$jobreport = logging("Starting backup of individual site "+$sitecollectionurl+" to "+$backupdestination+"\"+$backupfilename+$guid+".backup")

#Perform backup if backup destination path is valid
if(test-path $backupdestination)
{
& $hive\BIN\stsadm.exe -o backup -url $sitecollectionurl -filename $backupdestination"\"$backupfilename$guid.backup -overwrite > $null
[DateTime]::Now.ToString() + ": Site backup Done! File name is $backupdestination\$backupfilename$guid.backup" >> "$backupdestination\log.txt"
}
else
{
$jobstatus="Error"
$jobreport = logging("Error: The backupdestination Path doesn't exists for site id "+$id)
}
}
else
{
$jobreport = logging("Skipping backup of individual site "+$sitecollectionurl)
}

Start-Sleep 10

# ---
# --- Do a farm backup if catastrophicbackup=1 ---
# ---
If ([string]::Compare($catastrophicbackup, "0", $True))
{
$jobreport = logging("Starting catastrophic backup of farm to "+$backupdestination+"\")

#Perform backup if backup destination path is valid
if(test-path $backupdestination)
{
& $hive\BIN\stsadm.exe -o backup -directory $backupdestination"\" -backupmethod $catastrophicmethod -overwrite > $null
[DateTime]::Now.ToString() + ": Farm backup Done! Destination is $backupdestination\" >> "$backupdestination\log.txt"
}
else
{
$jobstatus="Error"
$jobreport = logging("Error: The backupdestination Path doesn't exists for site id "+$id)
}
}
else
{
$jobreport = logging("Skipping catastrophic backup of farm with xml site id "+$id)
}
sendemail
$jobreport=""
}
Write-host([Environment]::NewLine+[Environment]::NewLine+"SPBackup has ended, waiting 40 seconds for jobs to complete...")
Start-Sleep 40