Exchange Updates Released!

Microsoft has just released Exchange updates!

Exchange updates for 2010

Exchange 2010 Service Pack 3 Rollup 5 resolves various customer updates and includes previously released security bulletins:

  • 2887459 Public folder expiry time is set incorrectly in Exchange Server 2010 SP3
  • 2892257 Email items are lost when you move items between shared folders by using EWS delegate access
  • 2897935 “Cannot save the object ‘\FolderName'” error message when you try to replicate Exchange Server 2010 public folders
  • 2898908 EdgeTransport.exe crashes if the From field is empty in an email message
  • 2903831 Only a single character is allowed in the disclaimer content in ECP
  • 2904459 RPC Client Access service crashes if you add “Signed By” or “Send From” column in Outlook online mode
  • 2913413 RPC Client Access service crashes with an exception in Exchange Server 2010
  • 2913999 Meeting request body and instructions are lost in delegate’s auto-forwarded meeting request
  • 2916836 EdgeTransport.exe crashes when a transport rule sends a rejection message to an empty address
  • 2919513 Memory leak or memory corruption occurs in Exchange Server 2010
  • 2924971 RPC Client Access service stops when you select an inactive search folder in Outlook 2007 in an Exchange Server 2010 SP3 environment
  • 2926057 EdgeTransport.exe crashes if seek operation failed in Exchange Server 2010
  • 2927856 Incorrect recurring meeting if disclaimer transport rule is enabled in Exchange Server 2010

Exchange updates for 2013

Exchange 2013 SP1 improves security and compliance features:

  • Data Loss Preventions (DLP) features are improved.
  • S/MIME back in OWA which will allow OWA users to make use of signed and encrypted messages.

SCOM: Exclude a group of objects from another group of objects

Consider the situation in which you have a group in SCOM with dynamic members and you need to exclude a subset of the membership based on another group with dynamic members. This may occur with a group used for monitoring in a 7×24 environment; some SCOM objects may not need to be monitored, thus needing to be excluded from the 7×24 monitoring group used for alerting.

Kevin Holman’s System Center Blog is the inspiration for this post but here I’ll describe in detail how to exclude SCOM objects as opposed to Windows Computer objects.  There is not a straightforward change in the GUI that can accomplish the goal. The underlying management pack XML must be modified since there is a lack of a Not expression when creating group membership from the GUI.

The steps that must be completed to exclude a group of objects from another group of objects is the following at a high level.

  • Create a new group with dynamic group membership.
  • Put the new group into a new management pack.
  • Create a second group with dynamic group membership and put it into the new management pack.
  • Export and modify the management pack XML to exclude one group of objects from another group of objects.
  • Import the management pack back into SCOM
  • Verify that the changes exclude a group of objects from another group of objects.

For this exercise we will use these groups:

  • Test 7×24 – Contains all production objects.
  • Test Non 7×24 – Contains a subset of the members found in Test 7×24 which may be pre-production or overly chatty objects that require alert tuning.

Create a new group and name it “test 7×24”.

Create a new group in SCOM

Create new management pack, name it “Test Group Exclusion”. Click create to create the management pack.

Create a new management pack in SCOM

Next use the drop down to select “object” and click the Add button.  Then use insert and use “Display name” for the property then use “Contains” for the operator.  Finally enter the unique value of objects within your environment.  This example will include all objects with DN1, DN2, DN3, or DN4 which coincide with Exchange 2010 DAG node mailbox servers.  The last thing to do is to switch to using OR group by right clicking the text “AND group for Object (all of these are true)” and select “Switch to OR Group”.  Below is screenshot of what the group will look like before the XML file is edited.

Set up dynamic membership for group

Create a second group and name it “Test Non 7×24”. Select the same management pack that was created earlier which was named “Test Group Exclusion”.

On the Dynamic Members configuration click the create/edit rules and specify “Object”. Using the same setup as above in the Test 7×24 group, specify a subset of members.  For simplicity this example specifies DN1 and will be used to exclude these objects from the Test 7×24 group.  Finalize creating the groups by clicking through the remaining steps of the wizard.  Below is screenshot of what the group will look like before the XML file is edited.

Create a new group in SCOM

In the navigation pane select administration and then select management packs.  Locate the newly created management pack and export it.

Edit the management pack and identify the element ID for each group.  These are located at the very end of the file:

<LanguagePacks>
<LanguagePack ID="ENU" IsDefault="false">
<DisplayStrings>
<DisplayString ElementID="Test.Group.Exclustion">
<Name>Test Group Exclustion</Name>
</DisplayString>
<DisplayString ElementID="Folder_26016X4ecbx540f0bf85bd0x302f1be0">
<Name>Test Group Exclustion</Name>
</DisplayString>
<DisplayString ElementID="UINameSpaceXXXXXXXXXXXXXXXXX9d5.Group">
<Name>Test 7x24</Name>
</DisplayString>
<DisplayString ElementID="UINameSpaceXXXXXXXXXXXXXXXXX9d5.Group.DiscoveryRule">
<Name>Populate Test 7x24</Name>
<Description>This discovery rule populates the group 'Test 7x24'</Description>
</DisplayString>
<DisplayString ElementID="UINameSpaceeXXXXXXXXXXXXXXXXX64c.Group">
<Name>Test Non 7x24</Name>
</DisplayString>
<DisplayString ElementID="UINameSpaceXXXXXXXXXXXXXXXXX64c.Group.DiscoveryRule">
<Name>Populate Test Non 7x24</Name>
<Description>This discovery rule populates the group 'Test Non 7x24'</Description>
</DisplayString>
</DisplayStrings>
</LanguagePack>
</LanguagePacks>

Use the element ID for Test 7×24 to find the line with a matching GroupInstanceID:

<GroupInstanceId>$MPElement[Name="UINameSpaceXXXXXXXXXXXXXXXXX9d5.Group"]$</GroupInstanceId>

The first instance of an expression below this must be modified to include an AND expression.

Before:

<Expression>
<Or>
<Expression>
<RegExExpression>

After:

<Expression>
<And>
<Expression>
<Or>
<Expression>
<RegExExpression>

Additionally at the bottom of the same list of regular expressions the group to exclude is inserted with a NotContained expression.  The NotContained section must include the element ID of the group of objects that will be excluded from the 7×24 group.

Before:

<Pattern>dn4</Pattern>
</RegExExpression>
</Expression>
</Or>
</Expression>

After:

<Pattern>dn4</Pattern>
</RegExExpression>
</Expression>
</Or>
</Expression>
<Expression>
<NotContained>
<MonitoringClass>$MPElement[Name=" UINameSpaceXXXXXXXXXXXXXXXXX64c.Group "]$</MonitoringClass>
</NotContained>
</Expression>
</And>
</Expression>

Save the changes and import the management pack.  If you have made any errors the management pack import process will show an error and will fail on import.

Now it is time to review the groups after the changes made to the management pack. Switch to the Administration view in the navigation pane and display the properties of the Test Non 7×24 group and click the create/edit rules button on the dynamic members tab.  The default behavior is to use an AND group and you’ll notice that even though initially an OR group was used; the logic has reverted to default.  If you use additional expressions be sure to switch the logic to OR group otherwise the dynamic membership will not work, you’ll not have any group members.

Group default is to use AND group

Confirm that the Test Non 7×24 group contains the objects that should be excluded in the Test 7×24 group membership.  To verify the group membership right click on the Test Non 7×24 group and select View Group Members…

Next display the properties of the Test 7×24 group and view the dynamic members tab and click the create/edit rules.  There will be an error displayed for an unhandled exception which is related to the NotContained expression inserted into the management pack.

Expression causes error on SCOM group

Click continue on the error message to view the dynamic members query.  The query now contains an AND group as well as an OR group.  The last expression within the OR group will be blank, do not delete it.  This blank expression in the GUI represents the NotContained expression inserted into the management pack.

exclude a group of objects from another group of objects

Finally, confirm that the Test 7×24 group membership does not contain the objects that are members of the Test Non 7×24 group (the membership can take some time to update). To verify the group membership right click on the Test 7×24 group and select View Group Members…

If you have done everything correctly the group members in Test Non 7×24 should not be found in the Test 7×24 group.

The Importance of PowerShell Training

It is hard to argue that the based of knowledge created around PowerShell over the years has been something of a grass roots movement.  Don Jones, a well known author and expert on PowerShell, recently wrote an article for Redmond Magazine aimed at the IT decision maker.  He states the importance of Powershell training and that professional training for administrators is a must-have.  Even though the article is geared towards the IT decision maker within an organization,  there are some obvious take aways for the IT professional.  The point of the article, in his own words, of course is the following:

The moral of the story, of course, is to get your people trained.

He takes it a bit further by saying:

Find your best and brightest, the ones who can become your Automation Makers….You don’t need to train the entire team; the enterprise pattern is shaping up as having a relatively small number of bright, adaptable IT folks taking on the role of PowerShell Wrangler.

Read moreThe Importance of PowerShell Training

Seed an Exchange 2010 DAG database locally

If you have an Exchange 2010 DAG deployment it is very likely that you have multiple datacenters for disaster recovery scenarios.  You may find that you need to reseed a DAG database but since bandwidth is at a premium between sites, what can you do?  If you have multiple copies of a database in the same datacenter, the best scenario is to seed an Exchange 2010 DAG database locally.

Consider this situation:  You have  two DAG nodes in you DR site and one of them requires a hardware replacement.  It is possible to create another copy of the database on the other node in the same datacenter using the local database copy as the source.  Using this method you are able to preserve the database copies in the secondary datacenter.  When the hardware is replaced and the DAG node is functional, you can seed the database copies back to the original DAG node.    The following PowerShell commands should be used to seed an Exchange 2010 DAG database locally.  Let’s assume that you have ExSvr1 and ExSvr2 in the secondary datacenter.  Each holds a database copy from servers in the primary datacenter.  You must create a copy of DB01 on ExSrv2 before shutting down ExSvr1.

Create a new database copy on ExSvr2.  Use the database name as the identity and set the mailbox server where this copy will live.  Also set the activation preference and postpone the seeding.

Add-MailboxDatabaseCopy -Identity DB01 -MailboxServer ExSvr2 -ActivationPreference 4 –SeedingPostponed

Once the PowerShell command completes you will receive the following warning.
WARNING: Replication is suspended for database copy ‘DB01’ because the database copy needs to be seeded.

The next step is to begin the seed process by using the -SourceServer switch and specifying ExSvr1 which is currently hosting the database copy in the secondary datacenter.

Update-MailboxDatabaseCopy -Identity DB01ExSvr2 -SourceServer ExSvr1 -DeleteExistingFiles


Here is a link to the official Microsoft Exchange 2010 documentation on the Update-MailboxDatabaseCopy which provides an example on how to seed an Exchange 2010 DAG database locally using the -SourceServer switch.

WSUS Reporting with Powershell Part 3

Edit: Find the complete script here.

This is the final installation that concludes WSUS reporting with PowerShell.  In part 1 I explained the basics of connecting to WSUS with PowerShell and making a few basic queries.  In Part 2 I covered using a specific group in WSUS and creating a summary of the updates that those group members needed.  In this post I’ll explain the following:

  • Filter the results to trim down the report.
  • Use HTML formatting to prepare an email.
  • Send the report via an HTML formatted email.
# Record the last time that WSUS was syncronized with updates from Microsoft.
$LastSync = ($wsus.GetSubscription()).LastSynchronizationTime

# Rewrite the array and eliminate servers that have 0 for needed updates.
$SummaryStatus = $SummaryStatus | where { $_.neededcount -ne 0 } | sort server

# List a summary of changes in a special table leveraging the "First" table class style listed above.
$WSUSHead += "<table class=`"First`">`r`n"
# Note the LastSync time.
$WSUSHead += "<tr><td class=`"First`"><b>Last Sync:</b></td><td class=`"First`"> " + `
$LastSync + "</td></tr>`r`n"
$WSUSHead += "</Body>`r`n"
$WSUSHead += "</Style>`r`n"
$WSUSHead += "</Head>`r`n"

# Create a generic HTML Header to use throughout the script for the body of the `
#  email message with table styles to control the formatting of any tables present it it.
$HTMLHead = "<Html xmlns=`"http://www.w3.org/1999/xhtml`">`r`n"
$HTMLHead += "<Head>`r`n"
$HTMLHead += "<Style>`r`n"
$HTMLHead += "TABLE{border-width: 1px;border-style: outset;border-color: `
black;border-spacing: 1px;border-collapse: separate;}`r`n"
$HTMLHead += "TH{border-width: 1px;padding: 1px;border-style:
inset;border-color: black;}`r`n"
$HTMLHead += "TD{border-width: 1px;padding-left: 3px;padding-right: `
3px;border-style: inset;border-color: black;}`r`n"
$HTMLHead += "TABLE.First{border-style: none;}`r`n"
$HTMLHead += "TD.First{border-style: none;}`r`n"
$HTMLHead += "</Style>`r`n"
$HTMLHead += "</Head>`r`n"
$HTMLHead += "<Body>`r`n"

# Build a variable with HTML for sending a report.
$UpdatesHTML = $HTMLHead
# Continue building HTML with the updates needed
$UpdatesHTML += $SummaryStatus | convertto-html -Fragment `
@{ Label = "Server"; Expression = { $_.server } }, @{ Label = "Needed Count"; Expression = { $_.NeededCount } }, @{ Label = "Not Installed"; Expression = { $_.NotInstalledCount } }, `
@{ Label = "Downloaded"; Expression = { $_.DownloadedCount } }, @{ Label = "Pending Reboot"; Expression = { $_.InstalledPendingRebootCount } }, @{ Label = "Failed Updates"; Expression = { $_.FailedCount } }, `
@{ Label = "Needed"; Expression = { $_.Needed } }

# Add an assembly to fix up powershell HTML markup. Ensures all special characters
# are converted correctly.
Add-Type -AssemblyName System.Web
$UpdatesHTML = [System.Web.HttpUtility]::HtmlDecode($UpdatesHTML)

# Create HTML email by adding all the various HTML sections from above.
$MailMessage = "
<html>
<body>
$WSUSHead
$UpdatesHTML
</body>
</html>
"

# Get the date and time.
$DateTime = Get-Date -Format "ddd MM/dd/yyyy h:mm tt"
# Set subject line to include the $DateTime variable.
$EmailSubject = "Update Status for " + $DateTime

# Send an email with all the compiled data.
[string[]]$EmailTo = "babcockscott@get-mailbox.net"
Send-MailMessage -To $EmailTo `
-Subject $EmailSubject -From "WSUS Reporting@get-mailbox.net" `
-Body $MailMessage -BodyasHTML `
-SmtpServer "MailRelayServer"

An example of the WSUS Reporting HTML email output is below:

WSUS Reporting HTML Email

WSUS Reporting with Powershell Part 2

Edit: Find the complete script here.

In my previous post I provided the basics of connecting to WSUS with PowerShell and making a few basic queries that will be used in a WSUS reporting script.  This is the second post about WSUS reporting and covers how to query a group in WSUS and provide a summary of computers that need updates from the most recent synchronization.

This script will provide an output in PowerShell but it is intended to be used in conjunction with an email alert.  The report has a very nice feature that takes the needed updates and converts them into a hyperlink with the text being the KB article number.

# Create empty arrays to contain collected data.
$UpdateStatus = @()
$SummaryStatus = @()

# Use a foreach loop to process summaries per computer for each
# member of the "Email Servers" group. Then populate an array
# with a updates needed.
Foreach ($Object in $wsus.GetSummariesPerComputerTarget($updatescope, $computerscope)) {
	# Use a nested foreach to process the Mail Servers members.
	Foreach ($object1 in $MemberOfGroup) {
		# Use an if statement to match the wsus objects that contains update summaries with
		#  the members of the Mail servers members.
		If ($object.computertargetid -match $object1.id) {
			# Set the fulldomain name of the Mail Server member in a variable.
			$ComputerTargetToUpdate = $wsus.GetComputerTargetByName($object1.FullDomainName)
			# Filter the server for updates that are marked for install with the state
			#  being either downloaded or notinstalled.  These are updates that are needed.
			$NeededUpdate = $ComputerTargetToUpdate.GetUpdateInstallationInfoPerUpdate() `
			| where {
				($_.UpdateApprovalAction -eq "install") -and `
				(($_.UpdateInstallationState -eq "downloaded") -or `
				($_.UpdateInstallationState -eq "notinstalled"))
			}
			
			# Null out the following variables so that they don't contaminate
			#  op_addition variables in the below nested foreach loop.
			$FailedUpdateReport = $null
			$NeededUpdateReport = $null
			# Use a nested foreach loop to accumulate and convert the needed updates to `
			# the KB number with URL in an HTML format.
			if ($NeededUpdate -ne $null) {
				foreach ($Update in $NeededUpdate) {
					
					$myObject2 = New-Object -TypeName PSObject
					$myObject2 | add-member -type Noteproperty -Name Server `
					-Value (($object1 | select -ExpandProperty FullDomainName) `
					-replace ".get-mailbox.net", "")
					$myObject2 | add-member -type Noteproperty -Name Update `
					-Value ('<a>' + (($wsus.GetUpdate([Guid]$update.updateid)).title) + '<' + '/' + 'a' + '>')
					$UpdateStatus += $myObject2
					
					if ($Update.UpdateInstallationState -eq "Failed") {
						$FailedUpdateReport += ('</a><a>' + "(" + (($wsus.GetUpdate([Guid]$update.updateid)).KnowledgebaseArticles) + ") " + '<' + '/' + 'a' + '>')
					}
					if ($Update.UpdateInstallationState -eq "Notinstalled" -or $Update.UpdateInstallationState -eq "Downloaded") {
						$NeededUpdateReport += ('</a><a>' + "(" + (($wsus.GetUpdate([Guid]$update.updateid)).KnowledgebaseArticles) + ") " + '<' + '/' + 'a' + '>')
					}
				}
			}
			# Create a custom PSObject to contain summary data about each server and updates needed.
			$myObject1 = New-Object -TypeName PSObject
			$myObject1 | add-member -type Noteproperty -Name Server -Value (($object1 | select -ExpandProperty FullDomainName) -replace ".get-mailbox.net", "")
			$myObject1 | add-member -type Noteproperty -Name UnkownCount -Value $object.UnknownCount
			$myObject1 | add-member -type Noteproperty -Name NotInstalledCount -Value $object.NotInstalledCount
			$myObject1 | add-member -type Noteproperty -Name NotApplicable -Value $object.NotApplicableCount
			$myObject1 | add-member -type Noteproperty -Name DownloadedCount -Value $object.DownloadedCount
			$myObject1 | add-member -type Noteproperty -Name InstalledCount -Value $object.InstalledCount
			$myObject1 | add-member -type Noteproperty -Name InstalledPendingRebootCount -Value $object.InstalledPendingRebootCount
			$myObject1 | add-member -type Noteproperty -Name FailedCount -Value $object.FailedCount
			$myObject1 | add-member -type Noteproperty -Name ComputerTargetId -Value $object.ComputerTargetId
			$myObject1 | add-member -type Noteproperty -Name NeededCount -Value ($NeededUpdate | measure).count
			$myObject1 | add-member -type Noteproperty -Name Failed -Value $FailedUpdateReport
			$myObject1 | add-member -type Noteproperty -Name Needed -Value $NeededUpdateReport
			$SummaryStatus += $myObject1
		}
	}
}

# Only return the values that are most interesting.
$SummaryStatus | select server, notinstalled*, downloaded*, installedpending*, failed*, needed* | ft

The result of the $SummaryStatus variable with the select-object filters above results in the following:

WSUS Reporting

The final section of this series involves filtering the returned data a little further and then using some HTML formatting to send an acceptable looking report.

WSUS Reporting with Powershell Part 1

Edit: Find the complete script here.

If you have ever used WSUS for pushing Microsoft patches then you’ll know that getting a quick report of the patching status can be cumbersome. This is the first post about WSUS reporting that will lay out the basics of using PowerShell to connect to WSUS and make some queries about the status of your environment. In this post I will explain the following.

  • How to load the WSUS assembly.
  • Make a connection to WSUS.
  • Create a computer scope and update scope, which is needed for making queries.
  • Generate a few basic queries that will be used in the WSUS reporting script.

The very first thing that must be done is to load the WSUS assembly.

[reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration") | Out-Null

The next thing to do is to make a connection to WSUS and set the connection into a variable for future use. At the end of the command substitute the WSUS server name, use $true to force a connection via SSL and finally enter the port number.

$WSUS = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer("wsus.get-mailbox.net",$true,8531)

In order to make some queries within WSUS we’ll be using the GetSummariesPerComputerTarget method. This method requires a computer scope and additionally we will be using an update scope to refine the query. First create a default update scope and then modify it only include the latest approved revisions.

$UpdateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope
$UpdateScope.ApprovedStates = [Microsoft.UpdateServices.Administration.ApprovedStates]::LatestRevisionApproved

Next create the computer scope, no need to make a modification.

$ComputerScope = New-Object Microsoft.UpdateServices.Administration.ComputerTargetScope

Now that we have this bit established we can take a look at a few things. Run the following to return the groups that you have established in the WSUS console:

$WSUS.GetComputerTargetGroups()

Taking things a step further let’s identify one of the group ids and use Where-Object to filter a specific group and set it into a variable. Then enumerate all the members of the group:

$ComputerTargetGroups = $WSUS.GetComputerTargetGroups() `
| Where {$_.id -eq 'aa60f906-b555-4b81-bd3b-a050fde63ce8'}
$MemberOfGroup = $wsus.getComputerTargetGroup($ComputerTargetGroups.Id).GetComputerTargets()

You will now have detailed information about each computer object that is a member of your WSUS group stored in the $MemberOfGroup variable.  We’ll use this in the next section two find out what updates are needed for the members of this group and build upon the WSUS reporting script.

PowerShell Registry Update File MRU and Place MRU

Recently, as part of a data migration project and in an effort to create a seamless user experience in Microsoft Office, the recent documents and recent places needed to be updated with a new server name.  This prevented any users from using this Office feature and having problems.

Using PowerShell to make updates to the File MRU and Place MRU accomplishes this goal.

# Create an empty array to contain collected data for parsing.
$MRUArray = @()
# Get-ChildItem recursively searches for File MRU and Place MRU. Get-Item returns
#  Name, Propery and PSPath that are used for updating the registry.  The items
#  are added to the variable.
$MRU = Get-ChildItem HKCU:\Software -recurse -ea SilentlyContinue `
| where { $_.Name -like "*File MRU" -or $_.Name -like "*Place MRU" } | Get-Item

# Foreach item returned above and contained in $MRU.
foreach ($Item in $MRU) {
	# Process the current item that matches "item*" and pipe to a new foreach loop.
	$Item.property -like "item*" |
	foreach {
		# Create a new object and add PSPaath, Item, and MRUPath to the array.
		$RegObject = New-Object system.Object
		$RegObject | Add-Member -type NoteProperty -Name "PSPath" -Value $Item.PSPath
		$RegObject | Add-Member -type NoteProperty -Name "Item" -Value $_
		$RegObject | Add-Member -type NoteProperty -Name "MRUPath" -Value (Get-ItemProperty $Item.PSPath).$_
		$MRUArray += $RegObject
	}
}

# Foreach object in $MRUArray.
foreach ($object in $MRUArray) {
	# Process the current object and replace server name.
	set-itemproperty -Path $object.PSPath -Name $object.Item `
	-value $object.MRUPath.replace("OldServer", "NewServer")
}

 

Redistribute Mailboxes Across an Exchange 2010 DAG

I have put together a script to redistribute mailboxes across an Exchange 2010 DAG and wanted to share my work with the Exchange community.  Over time some of the databases in an Exchange 2010 DAG  will have an uneven distribution of mailboxes.  The script in this post automates the assignment of mailboxes for redistribution based on a couple of criteria, standard mailbox count, large mailbox count and total mailbox count.  These definition of standard and large mailbox sizes are different in just about every environment.  Therefore, I’m making the assumption that if a mailbox in your environment is using the default database quotas, it is a standard mailbox.  Otherwise, the mailbox is not using the default database quotas and has a different, larger quota that is uniform across the board.  The high level steps are:

  • Gather all DAG databases.
  • Gather mailboxes on each database and categorize as standard or large.
  • Make calculations about each mailbox type per DAG database so that we can get accurate tallies and then evenly redistribute mailboxes.
  • Make new-moverequest assignments and track the changes that will occur on the mailbox count per database
  • Initiate the new-moverequest for mailboxes selected.

This script will require the Exchange 2010 snapin.

 Microsoft.Exchange.Management.PowerShell.E2010 -ErrorAction "SilentlyContinue"

Set a variable for the new-moverequest batch name.

$BatchName = RebalanceDAG

Get the databases within the DAG but skip recovery databases. Hopefully this makes the database gather process as generic as possible.

# Get all the DAG databases
$TarDatabases = Get-MailboxDatabase | where {$_.mastertype `
-eq "DatabaseAvailabilityGroup" -and $_.Recovery -eq $False}

We’ll use $DBCountArray for tracking the mailbox count on each database. $MBXArray will be used to store every mailbox in your organization and leveraged to make each new-moverequest.

# Create empty arrays to store sorting data.  One for Database counts and one `
#  for mailbox types and move request assignments.
$DBCountArray = @()
$MBXArray = @()

In this next code block we are taking each DAG database and then enumerating the mailboxes it contains.  This will take some time to execute so a progress bar is helpful here.  There are two variables used as counters (line 15 and 16) which are set to zero.  In the second foreach loop the script examines the current mailbox and determines if it is standard or large based on it using default database quotas or not.  The MBXObject variable records basic data about the mailbox and puts this data into the MBXArray.  At the end of the first foreach loop the script records each mailbox processed and tallies the count for each database.

# Build the list of databases with associated counts for the various criteria used during sorting.
Foreach ($TarDatabase in $TarDatabases) {
# Show a status bar for progress while data is collected.
$PercentComplete = [Math]::Round(($LoopCount++ / $TarDatabases.Count * 100),1)
$CurrentDB = $TarDatabase.Name
Write-Progress -Activity "Mailbox Database Query in Progress" -PercentComplete $PercentComplete `
-Status "$PercentComplete% Complete" -CurrentOperation "Current Database: $CurrentDB"

# Query the current database in the foreach loop and get every mailbox.
$TarDBInfo = Get-Mailbox -Database $TarDatabase

# Set the initial values to 0 for each database being processed.
$TotalActiveSTDMBs = 0
$TotalActiveLRGMBs = 0
# Loop through each mailbox in the database checking to see if it is a large mailbox or not.
Foreach ($MBXInfo in $TarDBINfo) {
$MBXObject = $null
$MBXObject = New-Object system.Object
$MBXObject | Add-Member -type NoteProperty -Name "Database" -Value $MBXInfo.database.Name
$MBXObject | Add-Member -type NoteProperty -Name "Name" -Value $MBXInfo.Name
$MBXObject | Add-Member -type NoteProperty -Name "PrimarySMTP" -Value $MBXInfo.PrimarySmtpAddress
If ($MBXInfo.UseDatabaseQuotaDefaults -eq $true) {
# It's a standard mailbox size so increment that number.
$TotalActiveSTDMBs++
$MBXObject | Add-Member -type NoteProperty -Name "MBXSize" -Value "Standard"
} Else {
# It's a large mailbox size so increment that number.
$TotalActiveLRGMBs++
$MBXObject | Add-Member -type NoteProperty -Name "MBXSize" -Value "Large"
}
$MBXObject | Add-Member -type NoteProperty -Name "MoveRequest" -Value $Null
$MBXArray += $MBXObject
# Store the collected data in the DBCountArray variable.
$DBObject = $Null
$DBObject = New-Object system.Object
$DBObject | Add-Member -type NoteProperty -Name "Database" -Value $TarDatabase
$DBObject | Add-Member -type NoteProperty -Name "StandardMailbox" -Value $TotalActiveSTDMBs
$DBObject | Add-Member -type NoteProperty -Name "LargeMailbox" -Value $TotalActiveLRGMBs
$DBObject | Add-Member -type NoteProperty -Name "TotalMailboxes" -Value ($TotalActiveSTDMBs + $TotalActiveLRGMBs)
$DBCountArray += $DBObject
}

We now take the $DBCountArray and pipe the collected counts to measure-object to get an average number for each of the three attributes; totalmailboxes, largemailbox, and standardmailbox.  Secondly use [Math] in order to round the averages to a whole number.

We’ll use all the calculated variables to continue building out what the database counts are within $DBCountArray.

# Measure the criteria counts to obtain various data. Average will be used to judge
# what each database count should be.
$TotalMBs = $DBCountArray | Measure-Object "TotalMailboxes" -Average
$TotalLMBs = $DBCountArray | Measure-Object "LargeMailbox" -Average
$TotalStdMBs = $DBCountArray | Measure-Object "StandardMailbox" -Average

# Round the averages.
$TotalMBAvg = ([Math]::Round($TotalMBs.average))
$TotalLMBAvg = ([Math]::Round($TotalLMBs.average))
$TotalStdMBAvg = ([Math]::Round($TotalStdMBs.average))

In this block the script matches the current database in the foreach loop with where-object and makes a calculation on each criteria by subtracting the average value from the total value and adding the property to the $DBCountArray.  This lets us know where each database is in relation to the average.  The calculated number will be positive integer, negative integer, or will be zero.  This will be the basis on which database to move mailboxes from and where to move them to.

# Using the averages obtained get the variance on the current database counts.
Foreach ($DB in $DBCountArray){
$DBCountArray | where {$_.database.name -eq $DB.database.Name} `
| Add-Member -type NoteProperty -Name "StdMbxDiff" -Value ($DB.Standardmailbox - $TotalStdMBAvg)
$DBCountArray | where {$_.database.name -eq $DB.database.Name} `
| Add-Member -type NoteProperty -Name "LrgMbxDiff" -Value ($DB.Largemailbox - $TotalLMBAvg)
$DBCountArray | where {$_.database.name -eq $DB.database.Name} `
| Add-Member -type NoteProperty -Name "TotMbxDiff" -Value ($DB.TotalMailboxes - $TotalMBAvg)
}

This section handles new-moverequest assignments for large mailboxes and tracking the database counts for each large mailbox.  The source databases are selected by sorting the totmbxdiff property using the descending switch.  Selecting the first one will always grab the database in the list that is farthest out of skew from the computed average.  I’ve devised a non elegant way to add and subtract each property in the array.  By using the do until code block the script nulls out the source and target during each iteration and will continue until either to source or target databases have reached zero, or in other words, until it runs out of mailboxes to move or a place to move them to.

# Loop through and assess the count on the Database count, increment the various criteria and make new move assignments.
Write-Host "Assigning Large Mailboxes" -ForegroundColor Yellow
do {
# initialiaze/null out the variables to prevent contaminating the loop.
$DBsource = $null
$DBtarget = $null
# Get the large mailbox count source and target databases
$DBsource = $DBCountArray | where {$_.lrgmbxdiff -gt 0} | sort totmbxdiff -Descending | select -First 1
$DBtarget = $DBCountArray | where {$_.lrgmbxdiff -lt 0} `
| sort totmbxdiff | select -First 1
# If they aren't null then do the work.
if ($DBsource -ne $null -and $DBtarget -ne $null){
# decrement the source databases
($DBCountArray | where {$_.database.name -eq $DBsource.database}).largemailbox = ($DBCountArray | where {$_.database.name -eq $DBsource.database}).largemailbox -1
($DBCountArray | where {$_.database.name -eq $DBsource.database}).totalmailboxes = ($DBCountArray | where {$_.database.name -eq $DBsource.database}).totalmailboxes -1
($DBCountArray | where {$_.database.name -eq $DBsource.database}).lrgmbxdiff = ($DBCountArray | where {$_.database.name -eq $DBsource.database}).lrgmbxdiff -1
($DBCountArray | where {$_.database.name -eq $DBsource.database}).totmbxdiff = ($DBCountArray | where {$_.database.name -eq $DBsource.database}).totmbxdiff -1
# increment the target databases
($DBCountArray | where {$_.database.name -eq $DBtarget.database}).largemailbox = ($DBCountArray | where {$_.database.name -eq $DBtarget.database}).largemailbox +1
($DBCountArray | where {$_.database.name -eq $DBtarget.database}).totalmailboxes = ($DBCountArray | where {$_.database.name -eq $DBtarget.database}).totalmailboxes +1
($DBCountArray | where {$_.database.name -eq $DBtarget.database}).lrgmbxdiff = ($DBCountArray | where {$_.database.name -eq $DBtarget.database}).lrgmbxdiff +1
($DBCountArray | where {$_.database.name -eq $DBtarget.database}).totmbxdiff = ($DBCountArray | where {$_.database.name -eq $DBtarget.database}).totmbxdiff +1
# Access the list of mailboxes and make an assigment based on the target identified in $DBtarget
($MBXArray | where {$_.database -eq $DBsource.Database -and $_.mbxsize -eq "large" -and $_.moverequest -eq $null} | select -First 1).moverequest = $DBtarget.Database.Name
}
}
# Keep doing the loop until either variable runs out of databases.
until ($null -eq $DBtarget -or $null -eq $DBsource)

This next section of code is rinse and repeat for the standard mailboxes.

# Loop through and assess the count on the Database count, increment the various criteria and make new move assignments.
Write-Host "Assigning Standard Mailboxes" -ForegroundColor Yellow
do {
# initialiaze/null out the variables to prevent contaminating the loop.
$DBsource = $null
$DBtarget = $null
# Get the standard mailbox count source and target databases
$DBsource = $DBCountArray | where {$_.stdmbxdiff -gt 0} | sort totmbxdiff -Descending | select -First 1
$DBtarget = $DBCountArray | where {$_.stdmbxdiff -lt 0} | sort totmbxdiff | select -First 1
# If they aren't null then do the work.
if ($DBsource -ne $null -and $DBtarget -ne $null){
# decrement the source databases
($DBCountArray | where {$_.database.name -eq $DBsource.database}).standardmailbox = ($DBCountArray | where {$_.database.name -eq $DBsource.database}).standardmailbox -1
($DBCountArray | where {$_.database.name -eq $DBsource.database}).totalmailboxes = ($DBCountArray | where {$_.database.name -eq $DBsource.database}).totalmailboxes -1
($DBCountArray | where {$_.database.name -eq $DBsource.database}).stdmbxdiff = ($DBCountArray | where {$_.database.name -eq $DBsource.database}).stdmbxdiff -1
($DBCountArray | where {$_.database.name -eq $DBsource.database}).totmbxdiff = ($DBCountArray | where {$_.database.name -eq $DBsource.database}).totmbxdiff -1
# increment the target databases
($DBCountArray | where {$_.database.name -eq $DBtarget.database}).standardmailbox = ($DBCountArray | where {$_.database.name -eq $DBtarget.database}).standardmailbox +1
($DBCountArray | where {$_.database.name -eq $DBtarget.database}).totalmailboxes = ($DBCountArray | where {$_.database.name -eq $DBtarget.database}).totalmailboxes +1
($DBCountArray | where {$_.database.name -eq $DBtarget.database}).stdmbxdiff = ($DBCountArray | where {$_.database.name -eq $DBtarget.database}).stdmbxdiff +1
($DBCountArray | where {$_.database.name -eq $DBtarget.database}).totmbxdiff = ($DBCountArray | where {$_.database.name -eq $DBtarget.database}).totmbxdiff +1
# access the list of mailboxes and make an assignment based on the target identified in $DBtarget
($MBXArray | where {$_.database -eq $DBsource.Database -and $_.mbxsize -eq "standard" -and $_.moverequest -eq $null} | select -First 1).moverequest = $DBtarget.Database.Name
}
}
# Keep doing the loop until either variable runs out of databases.
until ($null -eq $DBtarget -or $null -eq $DBsource)

If you have made it this far then take a peek at what has been generated in the $DBCountArray

$DBCountArray | ft -AutoSize

Taking a look at the contents of the $MBXArray you will see something similar.  This example list shows standard mailboxes but the mailboxes are not assigned a database for a move request.  The $MBXArray will be used with the new-moverequest cmdlet to direct mailboxes to their new databases.

$MBXArray

The final step is to initiate the new-move request.  This is accomplished by taking the $MBXArray and trimming out any mailboxes without the moverequest property populated using where-object.  The $MoveRequest variable contains all the mailboxes that will be moved so simply pipe that variable into a ForEach loop that creates a new move request.

# New-Move request to initiate seeding of mailboxes to databases as assigned in $MailboxData table.
$MoveRequest = $MBXArray | where {$_.moverequest -ne $null}
$MoveRequest | ForEach {
$TargetDB = $_.DatabaseName
$ID = $_.PrimarySMTPAddress
New-MoveRequest -identity $ID -TargetDatabase $TargetDB -BadItemLimit 50 -Batchname $BatchName -SuspendWhenReadyToComplete -Confirm:$false
}

Here is the script in it’s entirety:

# Get all the DAG databases
$TarDatabases = Get-MailboxDatabase | where {$_.mastertype -eq "DatabaseAvailabilityGroup" -and $_.Recovery -eq $False}

# Create empty arrays to store sorting data.  One for Database counts and one `
#  for mailbox types and move request assignments.
$DBCountArray = @()
$MBXArray = @()

# Build the list of databases with associated counts for the various criterea `
#  used durning sorting.
Foreach ($TarDatabase in $TarDatabases) {
# Show a status bar for progress while data is collected.
$PercentComplete = [Math]::Round(($LoopCount++ / $TarDatabases.Count * 100),1)
$CurrentDB = $TarDatabase.Name
Write-Progress -Activity "Mailbox Database Query in Progress" -PercentComplete $PercentComplete `
-Status "$PercentComplete% Complete" -CurrentOperation "Current Database: $CurrentDB"

# Define the query which which finds every mailbox in the database.
$TarDBInfo = Get-Mailbox -Database $TarDatabase
# Set the initial values to 0 for each database being processed.
$TotalActiveSTDMBs = 0
$TotalActiveLRGMBs = 0
# Loop through each mailbox in the database checking to see if it is a large mailbox or not
Foreach ($MBXInfo in $TarDBINfo) {
$MBXObject = $null
$MBXObject = New-Object system.Object
$MBXObject | Add-Member -type NoteProperty -Name "Database" -Value $MBXInfo.database.Name
$MBXObject | Add-Member -type NoteProperty -Name "Name" -Value $MBXInfo.Name
$MBXObject | Add-Member -type NoteProperty -Name "PrimarySMTP" -Value $MBXInfo.PrimarySmtpAddress

If ($MBXInfo.UseDatabaseQuotaDefaults -eq $true) {
# It's a standard mailbox size so increment that number.
$TotalActiveSTDMBs++
$MBXObject | Add-Member -type NoteProperty -Name "MBXSize" -Value "Standard"
} Else {
# It's a large mailbox size so increment that number.
$TotalActiveLRGMBs++
$MBXObject | Add-Member -type NoteProperty -Name "MBXSize" -Value "Large"
}
$MBXObject | Add-Member -type NoteProperty -Name "MoveRequest" -Value $Null
$MBXArray += $MBXObject
}
# Store the collected date in the DBCountArray variable.
$DBObject = $Null
$DBObject = New-Object system.Object
$DBObject | Add-Member -type NoteProperty -Name "Database" -Value $TarDatabase
$DBObject | Add-Member -type NoteProperty -Name "StandardMailbox" -Value $TotalActiveSTDMBs
$DBObject | Add-Member -type NoteProperty -Name "LargeMailbox" -Value $TotalActiveLRGMBs
$DBObject | Add-Member -type NoteProperty -Name "TotalMailboxes" -Value ($TotalActiveSTDMBs + $TotalActiveLRGMBs)
$DBCountArray += $DBObject
}

# Measure the critera counts to obtain various data. Average will be used to judge `
#  what each database count should be.
$TotalMBs = $DBCountArray | Measure-Object "TotalMailboxes" -Average -Maximum -Minimum -Sum
$TotalLMBs = $DBCountArray | Measure-Object "LargeMailbox" -Average -Maximum -Minimum -Sum
$TotalStdMBs = $DBCountArray | Measure-Object "StandardMailbox" -Average -Maximum -Minimum -Sum

# Round the averages.
$TotalMBAvg = ([Math]::Round($TotalMBs.average))
$TotalLMBAvg = ([Math]::Round($TotalLMBs.average))
$TotalStdMBAvg = ([Math]::Round($TotalStdMBs.average))

# Using the averages obtained get the variance on the current database counts.
Foreach ($DB in $DBCountArray){
# Blackberry and Large Mailbox
$DBCountArray | where {$_.database.name -eq $DB.database.Name} | Add-Member -type NoteProperty -Name "StdMbxDiff" -Value ($DB.Standardmailbox - $TotalStdMBAvg)
$DBCountArray | where {$_.database.name -eq $DB.database.Name} | Add-Member -type NoteProperty -Name "LrgMbxDiff" -Value ($DB.Largemailbox - $TotalLMBAvg)
$DBCountArray | where {$_.database.name -eq $DB.database.Name} | Add-Member -type NoteProperty -Name "TotMbxDiff" -Value ($DB.TotalMailboxes - $TotalMBAvg)
}

$DBCountArray | ft -AutoSize

# Loop through and assess the count on the Database count, increment the various `
#  criteria and make new move assignments.
Write-Host "Assigning Large Mailboxes" -ForegroundColor Yellow
do {
# initialiaze/null out the variables to prevent contaminating the loop.
$DBsource = $null
$DBtarget = $null
# Get the large mailbox count source and target databases
$DBsource = $DBCountArray | where {$_.lrgmbxdiff -gt 0} | sort totmbxdiff -Descending | select -First 1
$DBtarget = $DBCountArray | where {$_.lrgmbxdiff -lt 0} | sort totmbxdiff | select -First 1
# If they aren't null then do the work.
if ($DBsource -ne $null -and $DBtarget -ne $null){
# decrement the source databases
($DBCountArray | where {$_.database.name -eq $DBsource.database}).largemailbox = ($DBCountArray | where {$_.database.name -eq $DBsource.database}).largemailbox -1
($DBCountArray | where {$_.database.name -eq $DBsource.database}).totalmailboxes = ($DBCountArray | where {$_.database.name -eq $DBsource.database}).totalmailboxes -1
($DBCountArray | where {$_.database.name -eq $DBsource.database}).lrgmbxdiff = ($DBCountArray | where {$_.database.name -eq $DBsource.database}).lrgmbxdiff -1
($DBCountArray | where {$_.database.name -eq $DBsource.database}).totmbxdiff = ($DBCountArray | where {$_.database.name -eq $DBsource.database}).totmbxdiff -1
# increment the target databases
($DBCountArray | where {$_.database.name -eq $DBtarget.database}).largemailbox = ($DBCountArray | where {$_.database.name -eq $DBtarget.database}).largemailbox +1
($DBCountArray | where {$_.database.name -eq $DBtarget.database}).totalmailboxes = ($DBCountArray | where {$_.database.name -eq $DBtarget.database}).totalmailboxes +1
($DBCountArray | where {$_.database.name -eq $DBtarget.database}).lrgmbxdiff = ($DBCountArray | where {$_.database.name -eq $DBtarget.database}).lrgmbxdiff +1
($DBCountArray | where {$_.database.name -eq $DBtarget.database}).totmbxdiff = ($DBCountArray | where {$_.database.name -eq $DBtarget.database}).totmbxdiff +1
# access the list of mailboxes and make an assigment based on the target identified in $DBtarget
($MBXArray | where {$_.database -eq $DBsource.Database -and $_.mbxsize -eq "large" -and $_.moverequest -eq $null} | select -First 1).moverequest = $DBtarget.Database.Name
}
}
# Keep doing the loop until either variable runs out of databases.
until ($null -eq $DBtarget -or $null -eq $DBsource)

$DBCountArray | ft -AutoSize

# Loop through and assess the count on the Database count, increment the various `
#  criteria and make new move assignments.
Write-Host "Assigning Standard Mailboxes" -ForegroundColor Yellow
do {
# initialiaze/null out the variables to prevent contaminating the loop.
$DBsource = $null
$DBtarget = $null
# Get the standard mailbox count source and target databases
$DBsource = $DBCountArray | where {$_.stdmbxdiff -gt 0} | sort totmbxdiff -Descending | select -First 1
$DBtarget = $DBCountArray | where {$_.stdmbxdiff -lt 0} | sort totmbxdiff | select -First 1
# If they aren't null then do the work.
if ($DBsource -ne $null -and $DBtarget -ne $null){
# decrement the source databases
($DBCountArray | where {$_.database.name -eq $DBsource.database}).standardmailbox = ($DBCountArray | where {$_.database.name -eq $DBsource.database}).standardmailbox -1
($DBCountArray | where {$_.database.name -eq $DBsource.database}).totalmailboxes = ($DBCountArray | where {$_.database.name -eq $DBsource.database}).totalmailboxes -1
($DBCountArray | where {$_.database.name -eq $DBsource.database}).stdmbxdiff = ($DBCountArray | where {$_.database.name -eq $DBsource.database}).stdmbxdiff -1
($DBCountArray | where {$_.database.name -eq $DBsource.database}).totmbxdiff = ($DBCountArray | where {$_.database.name -eq $DBsource.database}).totmbxdiff -1
# increment the target databases
($DBCountArray | where {$_.database.name -eq $DBtarget.database}).standardmailbox = ($DBCountArray | where {$_.database.name -eq $DBtarget.database}).standardmailbox +1
($DBCountArray | where {$_.database.name -eq $DBtarget.database}).totalmailboxes = ($DBCountArray | where {$_.database.name -eq $DBtarget.database}).totalmailboxes +1
($DBCountArray | where {$_.database.name -eq $DBtarget.database}).stdmbxdiff = ($DBCountArray | where {$_.database.name -eq $DBtarget.database}).stdmbxdiff +1
($DBCountArray | where {$_.database.name -eq $DBtarget.database}).totmbxdiff = ($DBCountArray | where {$_.database.name -eq $DBtarget.database}).totmbxdiff +1
# access the list of mailboxes and make an assigment based on the target identified in $DBtarget
($MBXArray | where {$_.database -eq $DBsource.Database -and $_.mbxsize -eq "standard" -and $_.moverequest -eq $null} | select -First 1).moverequest = $DBtarget.Database.Name
}
}
# Keep doing the loop until either variable runs out of databases.
until ($null -eq $DBtarget -or $null -eq $DBsource)

# Uncomment to double check your arrays
# $DBCountArray | ft -AutoSize
# $MBXArray | where {$_.moverequest -ne $null}

# New-Move request to initiate seeding of mailboxes to databases as assigned in $MailboxData table.
### The new-moverequest loop is commented out so you don't accidently initiate a move request
<# $MoveRequest = $MBXArray | where {$_.moverequest -ne $null} $MoveRequest | ForEach { $TargetDB = $_.DatabaseName $ID = $_.PrimarySMTPAddress New-MoveRequest -identity $ID -TargetDatabase $TargetDB -BadItemLimit 50 -Batchname $BatchName -SuspendWhenReadyToComplete -Confirm:$false } #>

PowerShell Try Catch Finally Error Handling

I’ve recently been experimenting with the script blocks in PowerShell try catch finally.  These script blocks can help you deal with terminating errors that may occur in your scripts.  A terminating error, otherwise known as an exception, will stop a command from processing the current object or from processing additional objects.  On the other hand, there are many situations that will result in a non-terminating error.  In a non-terminating error situation an error will generally be displayed on the console and continue processing.  An example of a non-terminating error is passing a variable containing a list of mailboxes to the Get-Mailbox cmdlet.  If an error occurs matching a name in the variable to an Exchange mailbox, the process continues but an error is displayed to the console.  A good resource for determining what exactly constitutes a terminating error or non-terminating error can be found in this MSDN article. This is where the script blocks try catch finally can be helpful. The try block is where you will put the code that you want to execute and monitor for errors.  If an error occurs then the code in the catch block is executed.  It is possible to include multiple catch blocks in order to catch certain types of errors.  The finally block runs no matter what happens during the try and catch script blocks.  The finally block can be used to provide informational output gather from a script or free up resources, per Microsoft’s documentation.  You may even choose to omit the finally block as part of your script if you don’t feel that it is necessary.

So if you are using the try catch finally blocks and have non-terminating errors within the try block, the catch block ignores the errors and does not fire.  It is possible to force any error in the try block by using the -ErrorAction switch.  Setting the error action to stop forces the catch block to see all errors, either terminating or non-terminating.

Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010 -ErrorAction 'SilentlyContinue'
$ExchangeAlias = Read-Host "Please enter alias"

Try {
"Attempting to get-mailbox of $ExchangeAlias"
Get-Mailbox $ExchangeAlias -ErrorAction stop
}

Catch {
$Error[0]
}

Finally {
Remove-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010 -ErrorAction 'SilentlyContinue'
}

You’ll notice in the catch block that I’m using the $Error variable. This is where PowerShell stores all the errors for a given session.  Using [0] after the $Error variable denotes accessing the first time in a PowerShell array so we’ll be returning the most recent error in $Error.  Just for the sake of the example, the finally block unloads the Exchange 2010 PowerShell snapin.

Ok so this script is reinventing the wheel a bit but the try catch finally script blocks are extremely useful for handling terminating errors or when you want to trap non-terminating errors in your code.