I've found that Exchange 2010 database copies (in a DAG group) have a tendency to fail if the DAG cluster is geographically distributed, and the WAN goes down occasionally. Microsoft specifies that connectivity should be redundant,etc, but that isn't a reality for some organizations who want to don't want the time or expense of maintaining a highly redundant infrastructure, when a slightly less redundant one will work 95% as well. That being said, we need a few scripts to mitigate the risks and address the issues in these scenarios.
The simple scenario is this:
Problems
Therefore we need three pieces of functionality that Microsoft does not provide:
The term "automatically" is key; although we could tie this to event logs or other types of triggers, we want to keep it simple and reliable, so we opted to use task scheduler to just run these Powershell scripts once every 15 minutes:
cd\
cls
#must have the DnsShell module from http://code.msdn.microsoft.com/dnsshell
Import-Module DnsShell
#load exchange2010 mgmt snapins
Add-PSSnapin Microsoft.Exchange.Management.Powershell.Support
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010
#input global variables and declarations
$CnameName = "Cname1.domain.com"
$CnameName2 = "Cname2.domain.com"
$cnames = $CnameName,$CnameName2
$DBname = "Database 1"
$DBname2 = "Database 2"
$DBs = $DBname,$DBname2
$Cname2DBArray = $DBname,$CnameName,$DBname2,$CnameName2
$MailServer01DNS = "mail1.domain.com."
$MailServer02DNS = "mail2.domain.com."
$Arecords = $MailServer01DNS,$MailServer02DNS
$server1 = "MailServer01"
$server2 = "MailServer02"
$servers = $server1,$server2
$EXCHarray = $server1,$MailServer01DNS,$server2,$MailServer02DNS
$DnsServerVar = "dns-server.domain.com"
$startstring="Start script run at: "
$startendtime=date
$startannounce=$startstring+$startendtime
#end input global variables
#begin functions
#function to write event to Windows if DB is moved
function writeEvent1111([string]$eventChangeMade)
{
$evt=new-object System.Diagnostics.EventLog("Application")
$evt.Source="Exchange DNS follow DB"
$infoevent=[System.Diagnostics.EventLogEntryType]::Warning
$1stpart="DNS follows Exchange DB Event: "
$2ndpart=" , see log for more details at C:\Program Files\Microsoft\Exchange Server\V14\Logging\Failback_logs"
$eventStringfull=$1stpart+$eventChangeMade+$2ndpart
$evt.WriteEntry($eventStringfull,$infoevent,1111)
}
#Gets the current server name with the specified database active
function getCurrentServerName([string]$feedMeDB)
{
$currentServerinfo = Get-MailboxDatabase -identity $feedMeDB
Write-Output "Getting DB server list from Exchange..."
Write-Output $currentServerinfo
Set-Variable -name CurrentServerName -value $currentServerinfo.server.name -scope global
}
#gets the current DNS name assocated with the CNAME record (what it's pointing to)
function getCurrentDNS([string]$currentCNAME)
{
Write-Output "Getting DNS records(s) from Domain Controllers..."
$dnsCMD1 = Get-DnsRecord $currentCNAME -Server $DnsServerVar
Set-Variable -name DNSget -value $dnsCMD1 -scope global
Set-Variable -name DNSrecord -value $DNSget.recordData -scope global
Set-Variable -name CnameID -value $DNSget.identity -scope global
}
#Make a change to the CNAME record with the new pointer passed to this function frm the if clause below that matches where it should be
function setDNSchange([string]$newHostName)
{
Write-Output "Making DNS change... "
set-dnsrecord -server $DnsServerVar -identity $CnameID -Hostname $newHostName
}
#exit the program cleanly, announcing the change that was made (function called only if a change was made)
function exitClean([string]$changeMade)#should always exit program here
{
$text1 = "The DNS record: "
$text2 = " was changed from: "
$text3 = " to: "
$finalConcat = $text1+$tempCNAME+$text2+$DNSrecord+$text3+$changeMade
Write-Output $finalConcat
writeEvent1111 $finalConcat
}
#end functions
#make sure you create this folder
Start-Transcript -Append -Force -Path 'C:\Program Files\Microsoft\Exchange Server\V14\Logging\Failback_logs\DNSfollowsExchDB.log'
$startannounce
" "
#begin main program
#step 1 - call the function to get the active server name and display the results
FOREACH ($db in $DBs)
{
getCurrentServerName $db;
$ServerNamePosition = 0..($EXCHarray.length - 1) | where {$EXCHarray[$_] -eq $CurrentServerName}
$DNSnamePosition = $ServerNamePosition + 1
$correctDNS = $EXCHarray[$DNSnamePosition]
$dbNamePosition = 0..($Cname2DBArray.length - 1) | where {$Cname2DBArray[$_] -eq $db}
$CnamePosition = $dbNamePosition + 1
$tempCNAME = $Cname2DBArray[$CnamePosition]
#step 2 - call the function to get the active DNS record and display the results
getCurrentDNS $tempCNAME
Write-Output "Current DNS Record is: "
Write-Output $DNSget
Write-Output "Current WMI location of this record is: "
Write-Output $CnameID
#step 3 - makes changes as needed
If ($DNSrecord -ne $correctDNS)
{setDNSchange $correctDNS ; exitClean $correctDNS}
else{Write-Output "dns is correct for " $db}
}
#end main program
" "
stop-transcript
#exit default if no changes made
exit
#this script should be run from the preferred server, which implies it is functional
# first subroutine to get and display status of each DB; if db is activated on the preferred server, the metric is in Exchange activation preference
# second subroutine to move each DB to the preferred server, the metric is in Exchange activation preference
## INCREASE WINDOW WIDTH #####################################################
#prepare evenlog but only use if db is moved
function WidenWindow([int]$preferredWidth)
{
[int]$maxAllowedWindowWidth = $host.ui.rawui.MaxPhysicalWindowSize.Width
if ($preferredWidth -lt $maxAllowedWindowWidth)
{
# first, buffer size has to be set to windowsize or more
# this operation does not usually fail
$current=$host.ui.rawui.BufferSize
$bufferWidth = $current.width
if ($bufferWidth -lt $preferredWidth)
{
$current.width=$preferredWidth
$host.ui.rawui.BufferSize=$current
}
# else not setting BufferSize as it is already larger
# setting window size. As we are well within max limit, it won't throw exception.
$current=$host.ui.rawui.WindowSize
if ($current.width -lt $preferredWidth)
{
$current.width=$preferredWidth
$host.ui.rawui.WindowSize=$current
}
#else not setting WindowSize as it is already larger
}
}
WidenWindow(120)
function exitClean([string]$DBmoved)#should always exit program here
{
$text1 = "The DB: "
$text2 = " was moved from server: "
$text3 = " to server: "
$finalConcat = $text1+$DBmoved+$text2+$xNow+$text3+$dbown.key.name
Write-Output $finalConcat
writeEvent1112 $finalConcat
}
#function to write event to Windows if DB is moved
function writeEvent1112([string]$eventChangeMade)
{
$evt=new-object System.Diagnostics.EventLog("Application")
$evt.Source="Exchange DB Failback"
$infoevent=[System.Diagnostics.EventLogEntryType]::Warning
$1stpart="DB Failback Event for Exchange DB, "
$2ndpart=" , see log for more details at C:\Program Files\Microsoft\Exchange Server\V14\Logging\Failback_logs"
$eventStringfull=$1stpart+$eventChangeMade+$2ndpart
$evt.WriteEntry($eventStringfull,$infoevent,1112)
}
$startstring="Start script run at: "
$startendtime=date
$startannounce=$startstring+$startendtime
#start Exchange Powershell snapins
cd\
Add-PSSnapin Microsoft.Exchange.Management.Powershell.Support
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010
#start FailbackExchDBtoPreferredServer
Start-Transcript -Append -Force -Path 'C:\Program Files\Microsoft\Exchange Server\V14\Logging\Failback_logs\failback.log'
$startannounce
" "
"Status"
Get-MailboxDatabase | Sort Name | FOREACH {$db=$_.Name; $xNow=$_.Server.Name ;$dbown=$_.ActivationPreference| Where {$_.Value -eq 1};$quoteon=" on ";$quotesb=" Should be on ";If ( $xNow -ne $dbOwn.Key.Name){$stat=" WRONG"; }ELSE{$stat=" OK" }; $OutP=$db+$quoton+$xNow+$quotesb+$dbOwn.Key.Name+$stat; write-output $OutP}
" "
"Moves (if any)"
Get-MailboxDatabase | Sort Name | FOREACH {$db=$_.Name; $xNow=$_.Server.Name ;$dbown=$_.ActivationPreference| Where {$_.Value -eq 1};$quoteon=" on ";$quotesb=" Should be on ";If ( $xNow -ne $dbOwn.Key.Name){$stat=" MOVING..."; }ELSE{$stat=" OK" }; $OutP=$db+$quoton+$xNow+$quotesb+$dbOwn.Key.Name+$stat; write-output $OutP; If ( $xNow -ne $dbOwn.Key){Move-ActiveMailboxDatabase $db -ActivateOnServer $dbown.key.name -Confirm:$False; exitClean $db }}
" "
stop-transcript
#end FailbackExchDBtoPreferredServer
exit
cd\
cls
#load exchange2010 mgmt snapins
Add-PSSnapin Microsoft.Exchange.Management.Powershell.Support
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010
#input global variables and declarations
$startstring="Start script run at: "
$startendtime=date
$startannounce=$startstring+$startendtime
#end input global variables
#begin functions
#function to write event to Windows if DB is moved
function writeEvent1113([string]$eventChangeMade)
{
$evt=new-object System.Diagnostics.EventLog("Application")
$evt.Source="Exchange attempt to update DB copy"
$infoevent=[System.Diagnostics.EventLogEntryType]::Warning
$1stpart="Exchange attempt to fix DB with Update: "
$2ndpart=" , see log for more details at C:\Program Files\Microsoft\Exchange Server\V14\Logging\Failback_logs"
$eventStringfull=$1stpart+$eventChangeMade+$2ndpart
$evt.WriteEntry($eventStringfull,$infoevent,1113)
}
#exit the program cleanly, announcing the change that was made (function called only if a change was made)
function exitClean([string]$changeMade)
{
$text1 = "The Database Copy: "
$text2 = " was attempted to be updated and re-mounted "
$finalConcat = $text1+$changeMade+$text2
Write-Output $finalConcat
writeEvent1113 $finalConcat
}
#attempt to update the failed database
function updateDBnow([string]$passedID)
{
Update-MailboxDatabaseCopy -Force -Identity $passedID
}
#end functions
Start-Transcript -Append -Force -Path 'C:\Program Files\Microsoft\Exchange Server\V14\Logging\Failback_logs\DNSfollowsExchDB.log'
$startannounce
" "
#begin main program
#identify any database copies in the failed state and pass their identity to the updateDBnow function
Get-MailboxDatabase | Sort Name | FOREACH{$db=$_.Name; Get-MailboxDatabaseCopyStatus -Identity $db | FOREACH{If($_.Status -eq "FailedandSuspended"){$PassID = $_.Identity;updateDBnow $PassID;exitClean $PassID}ELSE{write-host "Database: " $_.Name " is: " $_.Status " Mounted on: " $_.MailBoxServer}}}
#end main program
" "
stop-transcript
#exit default if no changes made
exit