Load balance new mailbox creation across multiple databases and servers using PowerShell

By Scott, May 27, 2009 2:42 PM

I needed a way to have new mailboxes be automatically distributed across a list of databases on multiple servers, and I recalled seeing a script somewhere.  The blog post with those details can be found here.  As the author points out, it can take a long time to build the hash table, too long for my requirements.  For example, one of my longer queries builds the table for 9 databases with over 5000 mailboxes in 58 seconds.  That is just way too  long for someone to feed a user account into to get a mailbox created.

So I broke down the query being used to find where efficiency could be improved.  The Get-ExchangeServer cmdlet slows the script down and can be avoided altogether by feeding the server list directly into the Get-MailboxDatabase cmdlet.  Additionally, I discovered just how inefficient the Exchange cmdlets are.  I already knew they were based on running other cmdlets for targets servers across a WAN that return not a lot of data, but this reinforced those findings.

To get away from the Exchange cmdlets, I query AD directly by instantiating ADSI in .NET manually.  Instead of slowly building the hash table I just take the record count of the search results and populate the table entry at once.  This reduced the time to completion from 58 seconds to 24 seconds.  These findings are consistent across different database counts and mailbox totals: 60% reduction in time.  While still too long for my taste, it certainly is much better than before and is within reason for the people at my company that make use of the script.

Since my script encompasses more than just the load balancing part (I create a GUI form where the location for the mailbox is selected, which feeds a list of appropriate servers to the function), this is just the part that creates the hash table with the database counts and returns the database with the lowest count:

$mbxServers = @()
$mailboxcount = @{}
$domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$root = [ADSI]"GC://$($domain.Name)"
$mbxServers | %{Get-MailboxDatabase -server $_} | ?{$_.recovery -eq $FALSE} | %{
$filter = "(&(objectcategory=user)(homeMDB=" + ($_.DistinguishedName) + "))"
$search = new-Object System.DirectoryServices.DirectorySearcher($root,$filter)
$search.PropertiesToLoad.Add("homeMDB") | Out-Null
$result = $search.FindAll()
$mailboxcount["$($_.Identity)"] = $result.count
}
($mailboxcount.GetEnumerator() | sort value | select -first 1).key

Minor update to last backup report (1.3.1)

By Scott, December 18, 2008 7:46 AM

Version 1.3.1 fixes a small issue where the subject would still say everything is okay even though the threshold for the last backup had been exceeded.  (I didn’t set the scope of the variable correctly after moving part of the code into a function.)  Download.

Powershell one-liner to remotely get SSL certificate expiration

By Scott, December 10, 2008 4:34 PM

This is a quick and dirty way of retrieving the self-signed SSL certificate that Exchange 2007 servers use out of the box.  Get-ExchangeCertificate only works locally on the box that has the certificate…not fun when you have a long list to find out so you can build a list for renewing them.  This uses psexec to connect to the remote server and retrieve any personal certificates and display the expiration date.  Short and sweet.

.\psexec \\server cmd /c "echo . | powershell (gci -path cert:\localmachine\my).notafter"

Exchange 2007 backup script updated to v1.3

By Scott, December 3, 2008 4:25 PM

This update fixes reporting for public folder databases.  I failed to account for the fact that PF databases won’t be returned when using Get-MailboxDatabase.  The structure of the script has changed a little bit to accommodate that, but the resulting change in the output is only that PF databases will now be included (and storage groups that only had a PF database won’t report that no databases are present).  Download v1.3.

Exchange 2007 backup script updated to v1.2

By Scott, August 21, 2008 10:51 AM

I added notification of what the checkmark means, automatically load the Exchange snapin if not already, and append the subject of the email with a status summary.  If there are no backups that need attention, all okay is added.  If there are, it says that attention is needed.  This provides a quick way to know if anything needs attention.  Download the script here.

Another minor update to the Exchange 2007 backup script

By Scott, August 11, 2008 4:00 PM

Version 1.11 correctly resets the checkmark status during each loop; when cleared before because no backup has taken place, it remained as a null value for later backups that were successful.  Updated version is available here.

Updated Exchange 2007 database backup script

By Scott, August 8, 2008 6:57 PM

I have made some minor updates to my PowerShell script that reports (and emails) the last backup of each database in your organization.  I was referencing the wrong variable when looping through the databases in a storage group, so each storage group in the report listed all the databases on the server.  I also change the formatting a little so the text in different table cells in the same row actually look to be in the same row (the checkmark was offsetting that cell a bit).  You can download v1.1 here.

PowerShell script to report last successful full backup of Exchange 2007

By Scott, July 22, 2008 7:28 AM

Edit: The inline code in this post is not the latest version of the script. Get the latest version from the downloads page.

This script is a port of my original backup report that was written in VBScript.  That script reports on both 2003 and 2007 servers, but lacked some of the features that I wanted to put in.  PowerShell natively supports date-awareness, which makes it much easier to add the number one feature I wanted to add: highlighting servers that haven’t had backups since a specified period of time.

Because I am using the native Exchange cmdlets instead of WMI or CDOEXM, this only reports on Exchange 2007 servers.  I figure accommodating both is more work than it is worth, so I just modified my VBScript version to not include any server in the Exchange 12 admin group and I have both run every day until my migration to 2007 is complete.

The script reports the last successful full backup of any Exchange 2007 server with the mailbox role installed.  It checks for the presence of storage groups and databases within them.  It notes if a backup is currently in progress, as well as if a backup has never completed.  If a backup has not completed in the last 72 hours (modifiable), it is highlighted in red so it is easy to spot.  If a backup is less than the defined number of hours old, I use the Marlett font to display a green checkmark.  This allows for a checkmark without having to reference an external image or embed one.  Lastly, the report is emailed.  The script is shown below, but you can also just download it.

#Last Backup Report for Exchange 2007 servers
#Version 1.0 - 7/9/08
#--------------------------------------------

#Begin customization-------------------------
$SmtpServer = "server.domain.com" #Enter FQDN of SMTP server
$SmtpFrom = "Exchange Backups <exchangebackupreport@domain.com>" #Enter sender email address
$SmtpTo = "user1@domain.com","user2@domain.com" #Enter one or more recipient addresses in an array
$SmtpSubject = "Exchange 2007 Last Backup Report" #Enter subject of message
$iNomHours = "72" #Enter number of hours since last backup that requires attention
#End customization---------------------------

$date = Get-Date
$sSpace = "&nbsp;&nbsp;&nbsp;&nbsp;"
$sOutput = "<table>"

#Checkmark to indicate last backup within nominal time
$sCheckMark = "<span style=""font-family: Marlett; color: green; font-size: 14pt; font-weight: bold"">a</span>"

#Retrieve Exchange servers with mailbox role
$ExServer = Get-ExchangeServer | where {$_.IsMailboxServer -eq $True} | Sort-Object Name
Foreach ($server in $ExServer)
	{
	$sOutput += "<tr><td><font size=2><u><b>$server</b></u></font></td><td></td></tr>"
	#Retrieve storage groups for a given server
	$StorageGroup = $server | Get-StorageGroup | Sort-Object Name
	#Check for absence of any storage groups
	If (($StorageGroup | Measure-Object Name).Count -eq $null)
		{
		$sOutput += "<tr><td><font size=2>" + $sSpace + "No storage groups present.</font></td></tr>"
		}
	Else
		{
		Foreach ($sg in $StorageGroup)
			{
			$sOutput += "<tr><td><font size=2>" + $sSpace + $sg.Name + "</font></td></tr>"
			#Retrieve mailbox databases for a given storage group
			$MailboxDatabase = $StorageGroup | Get-MailboxDatabase -Status | Sort-Object Name
			#Check for absence of any databases in storage group
			If (($MailboxDatabase | Measure-Object Name).Count -eq $null)
				{
				$sOutput += "<tr><td><font size=2>" + $sSpace + $sSpace + "No mailbox stores present.</font></td></tr>"				}
			Else
				{
				Foreach ($db in $MailboxDatabase)
					{
					$sBackupRunning = ""
					#Note if backup is currently running
					If ($db.BackupInProgress -eq $true)
						{$sBackupRunning = "<font size=2 color=blue>(Backup In Progress)</font>"}
					#Determine if backup has ever completed
					If ($db.LastFullBackup -ne $null)
						{
						$sBackupDay = $db.LastFullBackup.get_DayofWeek()
						$sBackupDateTime = $db.LastFullBackup.ToString("g")
						#Flag if last completed backup started longer than defined variable
						If (($date - $db.LastFullBackup).TotalHours -gt $iNomHours)
							{
							$sLastBackup = "<font size=2>Last Backup Started At: <font color=red>" + $sBackupDay + ", " + $sBackupDateTime + "</font></font>"
							}
						Else
							{
							$sLastBackup = "<font size=2>Last Backup Started At: " + $sBackupDay + ", " + $sBackupDateTime + " </font>" + $sCheckmark
							}
						}
					Else
						{
						$sLastBackup = "<font size=2>No full backup has completed yet.</font>"
						}
					$sOutput += "<tr><td><font size=2>" + $sSpace + $sSpace + $db.Name + " </font></td><td>" + $sLastBackup + $sBackupRunning + "</td></tr>"
					}
				}
			}
		}
	}
$sOutput += "</table>"

#Email results
$SmtpClient = New-Object System.Net.Mail.SmtpClient
$MailMessage = New-Object System.Net.Mail.MailMessage
$SmtpClient.Host = $SmtpServer
$MailMessage.From = $SmtpFrom
Foreach ($address in $smtpTo)
	{$MailMessage.To.Add($address)}
$MailMessage.Subject = $SmtpSubject
$MailMessage.IsBodyHTML = $true
$MailMessage.Body = $sOutput
$SmtpClient.Send($MailMessage)

Pocket Controller skin for AT&T tilt

By Scott, June 24, 2008 10:16 AM

If you use SOTI’s Pocket Controller Professional to control your Windows Mobile device from your desktop, you know that you can display the screen in a skin and control the virtual hardware buttons in PCP.  You can download skins from within PCP, but only during the first year of purchase.  They consider the skin catalog a "service."  After the year is up, you have to purchase the application again (at an upgrade price), which includes product upgrades for the year, too.  However, PCP hasn’t even been updated in a year, so it seems a pretty cheap way to earn revenue, considering SOTI even solicits images from customers to add to the skin catalog.  (They used to provide the entire skin catalog free of charge.)

My work installation is beyond the one year of service, so I couldn’t download a skin for my AT&T Tilt.  My home installation (separate license), however, was within the service year, so I downloaded the skin and copied it to my work installation.  I could not find anything in the program or on SOTI’s site regarding copyright of the skin images, so I am posting the Tilt images (displayed half-size) for anyone who needs it.

Tilt vertical image

Tilt horizontal image

Cannot enter a product key for a 64-bit Exchange 2007 server from the 32-bit management tools

By Scott, June 4, 2008 10:42 AM

The 32-bit version of Exchange 2007 cano be used in a non-production environment only.  As such, there is no need to license the product by entering a product key.  Doing so will only result in the error "Invalid product key".

A side effect of this limitation is that you can also not enter a product key for a valid 64-bit server from a 32-bit installation such as the management tools on a 32-bit OS.  I have a PowerShell script that configures a server, depending on the roles that are installed.  However, the first thing it does for all servers is apply the product key.  This fails because I am running my configuration script from a 32-bit management installation.

I consider this a bug.  The only solution is to run the Set-ExchangeServer command with the -ProductKey parameter on a 64-bit installation of the management tools.

Panorama Theme by Themocracy