Script: Exchange 2007 backups with NTBackup

Script: Exchange 2007 backups with NTBackup

NTBackup is a great solution for Exchange disaster recovery without buying expensive additions to your current backup software. We use NTBackup to backup each Exchange database to its own backup file. Afterwards our primary backup software will send each of those files to tape. Here is a script we use to dynamically backup all the databases on our Exchange servers using NTBackup.

Optimizing NTBackup

Technet has a great article on how Microsoft IT uses NTBackup to backup their Exchange 2003 clusters. This article recommends some registry entries to help optimize performance as well as details about an enhanced NTBackup version that is included with Windows 2003 Service Pack 1. These performance enhancements cut our backup times by more than half. To start the script I set these registry values.

#Add registry keys to enhance NTBackup performance
New-ItemProperty -Path:"HKCU:Software\Microsoft\Ntbackup\Backup Engine" -Name:"Logical Disk Buffer Size" -Value:"64" -PropertyType:"String" -Force
New-ItemProperty -Path:"HKCU:Software\Microsoft\Ntbackup\Backup Engine" -Name:"Max Buffer Size" -Value:"1024" -PropertyType:"String" -Force
New-ItemProperty -Path:"HKCU:Software\Microsoft\Ntbackup\Backup Engine" -Name:"Max Num Tape Buffers" -Value:"16" -PropertyType:"String" -Force

Detect Databases on Each Server

Next, the script will detect all the mailbox databases on the server. The same steps will be performed for public folder databases.

Get-MailboxDatabase -Server:$ServerName | foreach{
...

Creating the Backup Command File

Normally a ‘backup command’ or .bks file will be created by the NTBackup GUI. The script will create a .bks file for each database with the correct JET syntax and file encoding.

#Create the backup JET file for NTBackup
$JetBackupSyntax = "JET " + $_.ServerName + "\Microsoft Information Store\" + $_.StorageGroupName + "\" + $_.AdminDisplayName + "`n"
$BksFileName = $BackupScriptPath + $_.ServerName + "" + $_.StorageGroupName + "" + $_.AdminDisplayName + ".bks"
$JetBackupSyntax | out-file -filepath $BksFileName -encoding unicode

Invoking NTBackup

The ‘&’ character is an alias for the Invoke-Expression cmdlet. I use ‘cmd /c’ to ensure that the script waits for the completion of the database backup before continuing.

#Call NTBackup to backup the database
$BksDescriptiveName = $_.ServerName + "-" + $_.StorageGroupName.Replace(" ","_") + "-" + $_.AdminDisplayName.Replace(" ","_")
&cmd /c "C:\WINDOWS\system32\ntbackup.exe backup `"@$BksFileName`" /n $BksDescriptiveName /d $BksDescriptiveName /v:no /r:no /rs:no /hc:off /m normal /fu /j `"$BksDescriptiveName`" /l:s /f $BackupPath$BksDescriptiveName.bkf"

Compiling a Single Log File

Each backup process will create a log file in the format of backup##.log within the %userprofile%\Local Settings\Application Data\Microsoft\Windows NT\NTBackup\data\ directory. The script will add the contents of the latest backup file to the backup log for the server.

#Append database backup log to server backup log
&type (get-childitem "$Home\Local Settings\Application Data\Microsoft\Windows NT\NTBackup\data\" | Sort -Property:LastWriteTime -Descending)[0].FullName >> $BackupLog

At the end of the script, this file can be formatted and emailed to the system administrators.

#Add line breaks to format the backup log
$BackupLogFormatted = ""
Get-Content $BackupLog | foreach { $BackupLogFormatted += $_ + "`n" }

#Email the backup log
$smtp = New-Object Net.Mail.SmtpClient -arg $EmailSMTPServer
$smtp.Send($EmailReportFromAddress,$EmailReportTo,"Exchange Backup Results for " + $ServerName + ": " + $(Get-Date).ToString('MM/dd/yyyy'),$BackupLogFormatted)

Cluster Version

To backup our cluster we created separate cluster groups for our backup disk resource. This cluster group is moved to the corresponding active server to write the backup files. Afterwards it is moved to a passive server that sends the backup files to tape. We schedule this script to be run at the same time on all cluster nodes. It tests if a file path exists (that would be owned by an exchange virtual server) and if so perform the backup for the corresponding server node.

if (Test-Path G:\)
{
write-host ""
write-host "STARTING BACKUP (" $(get-date).Tostring("yyyy-MM-dd HH:mm:ss") ")"
write-host ""
write-host "** Getting Backup Disk..."
CLUSTER GROUP "EXEVS1 Backups" /MOVETO:$env:COMPUTERNAME
write-host "** Performing Backup..."

#Backup databases on EXEVS1 to the R:\ drive
perform_backup "EXEVS1" "R:\"

write-host "** Reassign Backup Disk to Server hit by TSM..."
CLUSTER GROUP "EXEVS1 Backups" /MOVETO:$BackupsServer
write-host ""
write-host "BACKUP COMPLETE (" $(get-date).Tostring("yyyy-MM-dd HH:mm:ss") ")"
write-host ""
}
elseif (Test-Path H:\)
{
write-host ""
write-host "STARTING BACKUP (" $(get-date).Tostring("yyyy-MM-dd HH:mm:ss") ")"
write-host ""
write-host "** Getting Backup Disk..."
CLUSTER GROUP "EXEVS2 Backups" /MOVETO:$env:COMPUTERNAME
write-host "** Performing Backup..."
perform_backup "EXEVS2" "S:\"
write-host "** Reassign Backup Disk to Server hit by TSM..."
CLUSTER GROUP "EXEVS2 Backups" /MOVETO:$BackupsServer
write-host ""
write-host "BACKUP COMPLETE (" $(get-date).Tostring("yyyy-MM-dd HH:mm:ss") ")"
write-host ""
}
elseif (Test-Path I:\)
{
write-host ""
write-host "STARTING BACKUP (" $(get-date).Tostring("yyyy-MM-dd HH:mm:ss") ")"
write-host ""
write-host "** Getting Backup Disk..."
CLUSTER GROUP "EXEVS3 Backups" /MOVETO:$env:COMPUTERNAME
write-host "** Performing Backup..."
perform_backup "EXEVS3" "T:\"
write-host "** Reassign Backup Disk to Server hit by TSM..."
CLUSTER GROUP "EXEVS3 Backups" /MOVETO:$BackupsServer
write-host ""
write-host "BACKUP COMPLETE (" $(get-date).Tostring("yyyy-MM-dd HH:mm:ss") ")"
write-host ""
}
else
{
write-host "--- NO BACKUP REQUIRED ---";
}

Scheduling the Task

Because this script uses PoweSshell with Exchange cmdlets you can’t just schedule the .ps1 file. You must run PowerShell, add in the Exchange PSConsoleFile, and invoke the script as a command.

C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe -PSConsoleFile “C:\Program Files\Microsoft\Exchange Server\bin\exshell.psc1” -command “c:\util\Backup-Databases.ps1”

The Script

function perform_backup ([string] $ServerName, [string] $BackupRootDrive) {

### Start User Defined Variables ###
#Set the backup file paths
$BackupScriptPath = $BackupRootDrive + "BackupFiles\"
$BackupPath = $BackupRootDrive + "DailyBackups\"

#Set email options
$EmailReportTo = "user@domain.com"
$EmailReportFromAddress = "user@domain.com"
$EmailSMTPServer = "smtp.domain.com"

$BackupLog = $BackupScriptPath + "ExchangeBackupLogs.log"

### End User Defined Variables ###

#Create an empty backup log file
new-item $BackupLog -type file -force

#Add registry keys to enhance NTBackup performance
New-ItemProperty -Path:"HKCU:Software\Microsoft\Ntbackup\Backup Engine" -Name:"Logical Disk Buffer Size" -Value:"64" -PropertyType:"String" -Force
New-ItemProperty -Path:"HKCU:Software\Microsoft\Ntbackup\Backup Engine" -Name:"Max Buffer Size" -Value:"1024" -PropertyType:"String" -Force
New-ItemProperty -Path:"HKCU:Software\Microsoft\Ntbackup\Backup Engine" -Name:"Max Num Tape Buffers" -Value:"16" -PropertyType:"String" -Force

Get-MailboxDatabase -Server:$ServerName | foreach{

#Create the backup JET file for NTBackup
$JetBackupSyntax = "JET " + $_.ServerName + "\Microsoft Information Store\" + $_.StorageGroupName + "\" + $_.AdminDisplayName + "`n"
$BksFileName = $BackupScriptPath + $_.ServerName + "" + $_.StorageGroupName + "" + $_.AdminDisplayName + ".bks"
$JetBackupSyntax | out-file -filepath $BksFileName -encoding unicode

#Call NTBackup to backup the database
$BksDescriptiveName = $_.ServerName + "-" + $_.StorageGroupName.Replace(" ","_") + "-" + $_.AdminDisplayName.Replace(" ","_")
&cmd /c "C:\WINDOWS\system32\ntbackup.exe backup `"@$BksFileName`" /n $BksDescriptiveName /d $BksDescriptiveName /v:no /r:no /rs:no /hc:off /m normal /fu /j `"$BksDescriptiveName`" /l:s /f $BackupPath$BksDescriptiveName.bkf"

#Append database backup log to server backup log
&type (get-childitem "$Home\Local Settings\Application Data\Microsoft\Windows NT\NTBackup\data\" | Sort -Property:LastWriteTime -Descending)[0].FullName >> $BackupLog
}
Get-PublicFolderDatabase -Server:$ServerName | foreach{

#Create the backup JET file for NTBackup
$JetBackupSyntax = "JET " + $_.ServerName + "\Microsoft Information Store\" + $_.StorageGroupName + "\" + $_.AdminDisplayName + "`n"
$BksFileName = $BackupScriptPath + $_.ServerName + "" + $_.StorageGroupName + "" + $_.AdminDisplayName + ".bks"
$JetBackupSyntax | out-file -filepath $BksFileName -encoding unicode

#Call NTBackup to backup the database
$BksDescriptiveName = $_.ServerName + "-" + $_.StorageGroupName.Replace(" ","_") + "-" + $_.AdminDisplayName.Replace(" ","_")
&cmd /c "C:\WINDOWS\system32\ntbackup.exe backup `"@$BksFileName`" /n $BksDescriptiveName /d $BksDescriptiveName /v:no /r:no /rs:no /hc:off /m normal /fu /j `"$BksDescriptiveName`" /l:s /f $BackupPath$BksDescriptiveName.bkf"

#Append database backup log to server backup log
&type (get-childitem "$Home\Local Settings\Application Data\Microsoft\Windows NT\NTBackup\data\" | Sort -Property:LastWriteTime -Descending)[0].FullName >> $BackupLog
}

#Add line breaks to format the backup log
$BackupLogFormatted = ""
Get-Content $BackupLog | foreach { $BackupLogFormatted += $_ + "`n" }

#Email the backup log
$smtp = New-Object Net.Mail.SmtpClient -arg $EmailSMTPServer
$smtp.Send($EmailReportFromAddress,$EmailReportTo,"Exchange Backup Results for " + $ServerName + ": " + $(Get-Date).ToString('MM/dd/yyyy'),$BackupLogFormatted)

}

### Start of backup script ###

#Define backup server
#The cluster backup resource will be moved to this server upon completion
$BackupsServer = "BackupServerName"

if (Test-Path G:\)
{
write-host ""
write-host "STARTING BACKUP (" $(get-date).Tostring("yyyy-MM-dd HH:mm:ss") ")"
write-host ""
write-host "** Getting Backup Disk..."
CLUSTER GROUP "EXEVS1 Backups" /MOVETO:$env:COMPUTERNAME
write-host "** Performing Backup..."

#Backup databases on EXEVS1 to the R:\ drive
perform_backup "EXEVS1" "R:\"

write-host "** Reassign Backup Disk to Server hit by TSM..."
CLUSTER GROUP "EXEVS1 Backups" /MOVETO:$BackupsServer
write-host ""
write-host "BACKUP COMPLETE (" $(get-date).Tostring("yyyy-MM-dd HH:mm:ss") ")"
write-host ""
}
elseif (Test-Path H:\)
{
write-host ""
write-host "STARTING BACKUP (" $(get-date).Tostring("yyyy-MM-dd HH:mm:ss") ")"
write-host ""
write-host "** Getting Backup Disk..."
CLUSTER GROUP "EXEVS2 Backups" /MOVETO:$env:COMPUTERNAME
write-host "** Performing Backup..."
perform_backup "EXEVS2" "S:\"
write-host "** Reassign Backup Disk to Server hit by TSM..."
CLUSTER GROUP "EXEVS2 Backups" /MOVETO:$BackupsServer
write-host ""
write-host "BACKUP COMPLETE (" $(get-date).Tostring("yyyy-MM-dd HH:mm:ss") ")"
write-host ""
}
elseif (Test-Path I:\)
{
write-host ""
write-host "STARTING BACKUP (" $(get-date).Tostring("yyyy-MM-dd HH:mm:ss") ")"
write-host ""
write-host "** Getting Backup Disk..."
CLUSTER GROUP "EXEVS3 Backups" /MOVETO:$env:COMPUTERNAME
write-host "** Performing Backup..."
perform_backup "EXEVS3" "T:\"
write-host "** Reassign Backup Disk to Server hit by TSM..."
CLUSTER GROUP "EXEVS3 Backups" /MOVETO:$BackupsServer
write-host ""
write-host "BACKUP COMPLETE (" $(get-date).Tostring("yyyy-MM-dd HH:mm:ss") ")"
write-host ""
}
else
{
write-host "--- NO BACKUP REQUIRED ---";
}

Thanks to :  Nick’s Exchange and Scripting Blog

Leave a Reply

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

ten + 12 =

This site uses Akismet to reduce spam. Learn how your comment data is processed.