This script is very handy to schedule and forget, if you have some monitoring software such as Labtech or Kaseya – you can look for the custom event log errors and alert if something didn’t go to plan (such as the store not starting again), well – let’s dissect the script and dig in!
Variables
As per most of my scripts now, I like to run my own little event log catalogue so any alerts are tracked and can be monitored, feel free to use this as a template – simply change the source name to something that works for you and document your own database of logs and codes.
#------- Assign Variables -------#
$Devices = gwmi -class Win32_LogicalDisk | where {$_.Description -like 'Local Fixed Disk' } | sort {$_.FreeSpace }
$TmpDrive = $Devices[$Devices.count - 1]
$ComputerName = hostname
#------- ######## -------#
#------- Event Log Variables -------#
$EventLog = New-Object System.Diagnostics.EventLog('Application')
$EventLog.MachineName = "."
$EventLog.Source = "Otori Logs"
function RaiseEvent {
Param([string]$Msg, [int]$EventLevel, [int]$EventID)
switch ($EventLevel){
0 { $el = "information" }
1 { $el = "warning" }
2 { $el = "error" }
default { $el = 'information' }
}
$EventLog.WriteEntry($Msg, [string]$el, $EventID)
}
#------- ######## -------#
Get Exchange Information
Now we’ll dig into active directory to obtain the Exchange Database information. This is then stored into an object and used later when we process each database.
#------- Search AD for Exchange Info -------#
# This will create a connection to LDAP
$Root = New-Object System.DirectoryServices.DirectoryEntry("LDAP://RootDSE")
$ConfigPartition = [adsi]("LDAP:// CN=Microsoft Exchange,CN=Services,"+$Root.configurationNamingContext)
$Searcher = New-Object System.DirectoryServices.DirectorySearcher($ConfigPartition)
# This will create a search in AD for Exchange Server Details
$Searcher.filter = ' (objectclass=msExchExchangeServer)'
$ExchDetails = $Searcher.FindAll() | where {$_.properties.name -like $Computername}
$ExchBin = [string]$ExchDetails.properties.msexchinstallpath+"\bin"
# This will create an array of private store databases
$Searcher.filter = ' (objectclass=msExchPrivateMDB)'
$PrivMDBs = $Searcher.FindAll() | where {$_.properties.msexchowningserver -like $ExchDetails.properties.distinguishedname}
# This will create an array of public store databases
$Searcher.filter = ' (objectclass=msExchPublicMDB)'
$PubMDBs = $Searcher.FindAll() | where {$_.properties.msexchowningserver -like $ExchDetails.properties.distinguishedname}
#------- ######## -------#
Database Processing Function
This function will do a few things, first we determine if there is a drive with enough free space to do the defrag (plus a little buffer room). If a drive doesn’t exist with enough free space, it will alert and not process the database. If however it’s all green lights, it will call eseutil and perform the offline defrag.
#------- Define Function for processing databases -------#
function ProcessDBs([object]$ExchDBs){
# Perform ESEUTIL on the databases if there is enough free space, if not send alert
$buffer = 1024000
foreach ($db in $ExchDBs){
$DBFile = $db.properties.msexchedbfile
$DBSize = (get-item $db.properties.msexchedbfile).length
$FreeSpace = $TmpDrive.FreeSpace + $buffer
$TMPDatabase = [string]$TmpDrive.DeviceID + "\tmp.edb"
if ( $DBSize -gt $FreeSpace ) {
#Notify there is not enough space to continue
$EventMessage = $Computername + " has attempted an Exchange offline defrag however it does not have enough space. " `
+ $TmpDrive.DeviceID + " has only " + $FreeSpace + " bytes free, however it requires " + $TmpDrive.FreeSpace `
+ " as the database size is " + $DBSize + " bytes. `n `n" `
+ "Offline Defrag has not occured for database located at " + $DBFile + "."
RaiseEvent $EventMessage 1 103
}else {
#Perform the offline defrag with eseutil and log progress
RaiseEvent ("Begin defrag of database located at " + $DBFile) 0 103
& $ExchBin\eseutil.exe /d $DBFile /t $TMPDatabase
RaiseEvent ("Complete defrag of database located at " + $DBFile) 0 103
}
}
}
#------- ######## -------#
Run the Process
Here the core section of the script is executed. First the Information Store must be stopped, then the databases are processed and finally the service started again.
#------- Main -------#
# Stop Exchange Services
RaiseEvent "Stopping Exchange Store" 0 103
stop-service msexchangeis
start-sleep -m 3
$MSExchangeIS = get-service msexchangeis
if ($MSExchangeIS.status -ne "Stopped"){
RaiseEvent ("Unable to stop the Exchange store") 2 103
exit
}
# Process each database on the server
ProcessDBs($PrivMDBs)
ProcessDBs($PubMDBs)
# Start Exchange Services
RaiseEvent "Starting Exchange Store" 0 103
start-service msexchangeis
start-sleep -m 3
$MSExchangeIS = get-service msexchangeis
if ($MSExchangeIS.status -ne "Stopped"){
RaiseEvent ("Exchange store has not started") 2 103
exit
}
#------- ######## -------#