Updated – Disabling Auto-mailbox mapping in Exchange 2010
In February this year I wrote about how to disable the automatic mapping of shared mailboxes in Exchange 2010 SP1, using a custom PowerShell script that "wraps" the Add-MailboxPermission command and after execution, removes the attribute in Active Directory that is used to automatically mount the mailbox in Outlook.
With Exchange 2010 SP2, there's great news - this workaround is no longer required, as in SP2 a new parameter has been added to the native cmdlet.
Check out the updated article here, and stay tuned for a future article explaining how to extend this functionality to the Exchange Management Console
Enabling Windows Live ID creation for On-Premises Mailboxes with Live@EDU
This is an article I’ve been meaning to write for a long time, however actually finding the answer to the problem has always been bottom of my priority list – primarily as no-one had actually asked me if it was possible. So I was quite glad to be challenged by Mike Crowley a couple of weeks ago to come up with a solution.
If you’re reading this and know Office 365, then I’ll take a moment to explain the differences between the way Live@EDU does things and the way Office 365 does it. Exchange Online in both services is pretty much the same – Outlook Live hosted in the same datacentres within the same Exchange Organization. They both synchronize users using the same underlying software, Identity Lifecycle Manager which underpins DirSync and OLSync (the Live@EDU equivalent).
However that’s pretty much where the similarities end. Whilst Office 365 users are synchronised against Microsoft Online Services IDs, Live@EDU users are synchronised against Windows Live IDs.. Yes, the same that are used to login to Messenger, Skydrive etc. Furthermore, unlike Office 365, user passwords are (or usually are, anyway) synchronized from the local Active Directory to the associated Windows Live IDs. That’s very different to Office 365, which relies on ADFS 2.0 to directly use local authentication, preventing passwords needing to be stored outside the local Active Directory.
The Problem
So, what’s the problem I’d been interested in overcoming? Well, OLSync doesn’t create Windows Live IDs (or synchronize passwords) for on-premises mailboxes; it only creates them for mailboxes hosted in Exchange Online.
Instead, it creates Mail Users in Exchange Online that can’t actually be logged on to. In quite a few circumstances, I think that’s a good thing. I don’t like the idea of most users in the organization having their password automatically pushed out to a Windows Live ID.
But (and there is always a but), not everyone has the same security requirements and the convenience of allowing all users – wherever their mailbox is located – access to Live services may be worth the risks. And, if you want to make use of Hybrid Coexistence features in the same way as Office 365 can, like Remote Mailbox moves, automatically creating and synchronizing Windows Live IDs with on-premises Mailboxes makes moving mailboxes back on forth a lot easier.
The Solution
When I was asked how to do this, I was half-convinced it couldn’t be done. I certainly had no intention of re-writing the management agent extension, and from a quick look in the past, didn’t see anything in the configuration and documentation that suggested that it was possible.
So, to cut a long story short, I used the trial of NET Reflector to have a peek inside the DLLs that make up the OLSync management agent and, as it turns out, every time a Mail User is created in Live@EDU for a corresponding On-Premises Mailbox, a flag is checked to see whether it should create a Windows Live ID.
The parameter to set to ensure it does create a Windows Live ID is DisableWLIDOnMailUser, which needs to be set to False in ILM, in the Configure Additional Parameters section of the Exchange Online management agent:
Once that’s set – and the other parameters are correct as per a normal installation, Windows Live IDs will be created for on-premises Mailboxes as they are synchronized.
If this is a new setup, all Mailboxes on-premise that would normally have a Live@EDU Mailuser created will be created with a corresponding Windows Live ID, which should get downlevel password changes the same way normal Live@EDU Mailboxes would from the on-premises Active Directory.
The one caveat is that for existing On-premise Mailboxes / Live@EDU Mailusers that have already been synchronized Windows Live IDs are not created – these need to be manually enabled using the following command:
In the future, this kind of fiddling won’t be neccessary – Office 365 for Education replaces Live@EDU and brings with it the much better ADFS 2.0 integrated Single Sign On and DirSync, which handles rich, hybrid coexistence scenarios a lot better. So I’ll leave you with the full list of optional parameters for you to try out if you are interested in seeing what else OLSync is capable of. Bear in mind though, these are probably unsupported by Microsoft – but could be the solution to something you’ve been trying to do for a while:
public enum ConfigurationParameterName { ClientRetryAttempts, AllowPartialDataImportWithTransientError, DisableWindowsLiveId, FederatedTenant, FederatedIdentitySourceAttribute, FederatedDelegationDomain, FederatedDelegationSourceAttribute, ProvisioningDomain, PasswordFile, FederatedNamespace, ResetPasswordOnNextLogon, NumberOfPages, ClientReportDirectory, DisableWLIDOnMailUser, PreferredDomainController, EnterpriseMode, TargetOU, ForestTrust, EvictLiveIdOnCreate, BypassAdminCountCheck, MVInitialPasswordAttributeName, MVWindowsLiveIdAttributeName, SyncProxyAddressProtocol, SyncGroupAsGroup, NameToDNSuffix, MailboxDeliveryDomain, OutlookLiveX500Sync, ConnectionCleanUpTime }
Generate Exchange Environment Reports using Powershell
Update 17th August - V1.5.4 - New features and bug fixes (see here for more information)
Update 24th July - V1.5.3 - Bug fixes (see here for more information)
Update 21th July - V1.5 - Re-write with new features including Exchange 2003 support for mixed environments and more detailed information (see here for more information)
Update 21th June - V1.1 - Bug fixes, Exchange 2007-only support in addition to 2010/2010+2007, and new features.
As an Exchange administrator, there’s times when it’s useful to have a visual, straightforward and concise document that gives you a good overview of your environment. Although with tools like Visio and Word you can make such a document, it’s hard to keep these documents up to date or use previous versions to track and check changes.
This script, inspired by the output of an Exchange TAP tool, aims to automatically generate a report that gives you an overview of your environment, Exchange 2003, 2007, 2010 servers and database availability groups - in particular:
- Total Servers per Exchange version & service pack
- Total Mailboxes per Exchange version & service pack
- Totals for Exchange roles across the environment
- A site-by-site breakdown for the following:
- Mailboxes per site
- Exchange servers, version, update rollup and version, service level, highlighted installed roles, OS version and service pack
- A breakdown of each Database Availability Group including:
- DAG name, member count and member list
- Database information such as
- Name
- Mailboxes per database and Average Size
- Archive mailboxes per database and Average Size - only shown if a DB includes Archive mailboxes
- Database and whitespace size
- Database and log disk free space percentage
- Last full backup date/time (new) - only shown if at least one DAG DB has had a full backup
- Circular Logging state (new) - only shown if at least one DAG DB has circular logging enabled
- Server hosting the active copy
- List of servers hosting copies and copy count
- A breakdown of Non-DAG databases including Exchange 2007 and 2003 DBs, including the database information above, along with Storage Group name (where applicable).
The script doesn’t support detailed information about Exchange 2007/2003 CCR/SCC clusters, but these are shown as ClusMBX in the output. At the moment, the script doesn’t show Public Folder information but if there is interest I can add extra features; and of course the source is provided should you wish to alter it to your own needs.
To be able to execute the script, you need to use the Exchange Management Shell (the latest version for your environment, with Powershell 2.0) and be able to get information about AD Sites, Exchange Servers, Mailboxes, Database Availability Groups and Databases. It uses WMI to retrieve OS information and detect Exchange 2007 clusters and calculate Exchange 2007 database size and Remote Registy calls to get Update Rollup information. A normal Exchange administrator should be able to perform these tasks.
Executing the script is straightforward – the only setting you need is to specify where to write the HTML file:
If you want it to email the results, the follow parameters are available to allow the report to be sent directly from the script:
Finally, to schedule the report to be generated nightly, execute with your preferred options and add the -ScheduleAs parameter, for example:
After generating the report, it will attempt to schedule the task and prompt (via schtasks.exe) for the password of the user you have chosen to schedule the report as.
As usual, the script is provided below as-is without any warranties. You can download the script ready to use at the bottom of this post. Comments and suggestions are always welcome.
.SYNOPSIS
Creates a HTML Report describing the Exchange environment
Steve Goodman
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE
RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
Version 1.5.4, 17th August 2011
.DESCRIPTION
This script creates a HTML report showing the following information about an Exchange 2010
and to a lesser extent, 2007 and 2003, environment. The following is shown:
* Report Generation Time
* Total Servers per Exchange Version (2003 > 2010)
* Total Mailboxes per Exchange Version and Organisation
* Total Roles in the environment
Then, per site:
* Total Mailboxes per site
* Exchange Servers with:
o Exchange Server Version
o Service Pack
o Update Rollup and rollup version
o Roles installed on server and mailbox counts
o OS Version and Service Pack
Then, per Database availability group (Exchange 2010):
* Total members per DAG
* Member list
* Databases, detailing:
o Mailbox Count and Average Size
o Archive Mailbox Count and Average Size (Only shown if DAG includes Archive Mailboxes)
o Database Size and whitespace
o Database and log disk free
o Last Full Backup (Only shown if one or more DAG database has been backed up)
o Circular Logging Enabled (Only shown if one or more DAG database has Circular Logging enabled)
o Mailbox server hosting active copy
o List of mailbox servers hosting copies and number of copies
Finally, per Database (Non DAG DBs/Exchange 2007/Exchange 2003)
* Databases, detailing:
o Storage Group (if applicable) and DB name
o Server hosting database
o Mailbox Count and Average Size
o Archive Mailbox Count and Average Size (Only shown if DAG includes Archive Mailboxes)
o Database Size and whitespace
o Database and log disk free
o Last Full Backup (Only shown if one or more DAG database has been backed up)
o Circular Logging Enabled (Only shown if one or more DAG database has Circular Logging enabled)
This does not detail public folder infrastructure, or examine Exchange 2007/2003 CCR/SCC clusters
(although it attempts to detect Clustered Exchange 2007/2003 servers, signified by ClusMBX).
IMPORTANT NOTE: The script requires WMI and Remote Registry access to Exchange servers from the server
it is run from to determine OS version, Update Rollup, Exchange 2007/2003 cluster and DB size information.
.PARAMETER HTMLReport
Filename to write HTML Report to
.PARAMETER SendMail
Send Mail after completion. Set to $True to enable. If enabled, -MailFrom, -MailTo, -MailServer are mandatory
.PARAMETER MailFrom
Email address to send from. Passed directly to Send-MailMessage as -From
.PARAMETER MailTo
Email address to send to. Passed directly to Send-MailMessage as -To
.PARAMETER MailServer
SMTP Mail server to attempt to send through. Passed directly to Send-MailMessage as -SmtpServer
.PARAMETER ScheduleAs
Attempt to schedule the command just executed for 10PM nightly. Specify the username here, schtasks (under the hood) will ask for a password later.
.PARAMETER ViewEntireForest
By default, true. Set the option in Exchange 2007 or 2010 to view all Exchange servers and recipients in the forest.
.PARAMETER ServerFilter
Use a text based string to filter Exchange Servers by, e.g. NL-* - Note the use of the wildcard (*) character to allow for multiple matches.
.EXAMPLE
Generate the HTML report
.\Get-ExchangeEnvironmentReport.ps1 -HTMLReport .\report.html
#>
param(
[parameter(Position=0,Mandatory=$true,ValueFromPipeline=$false,HelpMessage='Filename to write HTML report to')][string]$HTMLReport,
[parameter(Position=1,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Send Mail ($True/$False)')][bool]$SendMail=$false,
[parameter(Position=2,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Mail From')][string]$MailFrom,
[parameter(Position=3,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Mail To')]$MailTo,
[parameter(Position=4,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Mail Server')][string]$MailServer,
[parameter(Position=4,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Schedule as user')][string]$ScheduleAs,
[parameter(Position=5,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Change view to entire forest')][bool]$ViewEntireForest=$true,
[parameter(Position=5,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Server Name Filter (eg NL-*)')][string]$ServerFilter="*"
)
# Sub-Function to Get Database Information. Shorter than expected..
function _GetDAG
{
param($DAG)
@{Name = $DAG.Name.ToUpper()
MemberCount = $DAG.Servers.Count
Members = [array]($DAG.Servers | % { $_.Name })
Databases = @()
}
}
# Sub-Function to Get Database Information
function _GetDB
{
param($Database,$ExchangeEnvironment,$Mailboxes,$ArchiveMailboxes,$E2010)
# Circular Logging, Last Full Backup
if ($Database.CircularLoggingEnabled) { $CircularLoggingEnabled="Yes" } else { $CircularLoggingEnabled = "No" }
if ($Database.LastFullBackup) { $LastFullBackup=$Database.LastFullBackup.ToString() } else { $LastFullBackup = "Not Available" }
# Mailbox Average Sizes
$MailboxStatistics = [array]($ExchangeEnvironment.Servers[$Database.Server.Name].MailboxStatistics | Where {$_.Database -eq $Database.Identity})
if ($MailboxStatistics)
{
[long]$MailboxItemSizeB = 0
$MailboxStatistics | %{ $MailboxItemSizeB+=$_.TotalItemSizeB }
[long]$MailboxAverageSize = $MailboxItemSizeB / $MailboxStatistics.Count
} else {
$MailboxAverageSize = 0
}
# Free Disk Space Percentage
if ($ExchangeEnvironment.Servers[$Database.Server.Name].Disks)
{
foreach ($Disk in $ExchangeEnvironment.Servers[$Database.Server.Name].Disks)
{
if ($Database.EdbFilePath.PathName -like "$($Disk.Name)*")
{
$FreeDatabaseDiskSpace = $Disk.FreeSpace / $Disk.Capacity * 100
}
if ($Database.ExchangeVersion.ExchangeBuild.Major -ge 14)
{
if ($Database.LogFolderPath.PathName -like "$($Disk.Name)*")
{
$FreeLogDiskSpace = $Disk.FreeSpace / $Disk.Capacity * 100
}
} else {
$StorageGroupDN = $Database.DistinguishedName.Replace("CN=$($Database.Name),","")
$Adsi=[adsi]"LDAP://$($Database.OriginatingServer)/$($StorageGroupDN)"
if ($Adsi.msExchESEParamLogFilePath -like "$($Disk.Name)*")
{
$FreeLogDiskSpace = $Disk.FreeSpace / $Disk.Capacity * 100
}
}
}
} else {
$FreeLogDiskSpace=$null
$FreeDatabaseDiskSpace=$null
}
if ($Database.ExchangeVersion.ExchangeBuild.Major -ge 14 -and $E2010)
{
# Exchange 2010 Database Only
$CopyCount = [int]$Database.Servers.Count
if ($Database.MasterServerOrAvailabilityGroup.Name -ne $Database.Server.Name)
{
$Copies = [array]($Database.Servers | % { $_.Name })
} else {
$Copies = @()
}
# Archive Info
$ArchiveMailboxCount = [int]([array]($ArchiveMailboxes | Where {$_.ArchiveDatabase -eq $Database.Name})).Count
$ArchiveStatistics = [array]($ArchiveMailboxes | Where {$_.ArchiveDatabase -eq $Database.Name} | Get-MailboxStatistics -Archive )
if ($ArchiveStatistics)
{
[long]$ArchiveItemSizeB = 0
$ArchiveStatistics | %{ $ArchiveItemSizeB+=$_.TotalItemSize.Value.ToBytes() }
[long]$ArchiveAverageSize = $ArchiveItemSizeB / $ArchiveStatistics.Count
} else {
$ArchiveAverageSize = 0
}
# DB Size / Whitespace Info
[long]$Size = $Database.DatabaseSize.ToBytes()
[long]$Whitespace = $Database.AvailableNewMailboxSpace.ToBytes()
$StorageGroup = $null
} else {
$ArchiveMailboxCount = 0
$CopyCount = 0
$Copies = @()
# 2003 & 2007, Use WMI (Based on code by Gary Siepser, http://bit.ly/kWWMb3)
$Size = [long](get-wmiobject cim_datafile -computername $Database.Server.Name -filter ('name=''' + $Database.edbfilepath.pathname.replace("\","\\") + '''')).filesize
if (!$Size)
{
Write-Warning "Cannot detect database size via WMI for $($Database.Server.Name)"
[long]$Size = 0
[long]$Whitespace = 0
} else {
[long]$MailboxDeletedItemSizeB = 0
if ($MailboxStatistics)
{
$MailboxStatistics | %{ $MailboxDeletedItemSizeB+=$_.TotalDeletedItemSizeB }
}
$Whitespace = $Size - $MailboxItemSizeB - $MailboxDeletedItemSizeB
if ($Whitespace -lt 0) { $Whitespace = 0 }
}
$StorageGroup =$Database.DistinguishedName.Split(",")[1].Replace("CN=","")
}
@{Name = $Database.Name
StorageGroup = $StorageGroup
ActiveOwner = $Database.Server.Name.ToUpper()
MailboxCount = [long]([array]($Mailboxes | Where {$_.Database -eq $Database.Identity})).Count
MailboxAverageSize = $MailboxAverageSize
ArchiveMailboxCount = $ArchiveMailboxCount
ArchiveAverageSize = $ArchiveAverageSize
CircularLoggingEnabled = $CircularLoggingEnabled
LastFullBackup = $LastFullBackup
Size = $Size
Whitespace = $Whitespace
Copies = $Copies
CopyCount = $CopyCount
FreeLogDiskSpace = $FreeLogDiskSpace
FreeDatabaseDiskSpace = $FreeDatabaseDiskSpace
}
}
# Sub-Function to get mailbox count per server.
# New in 1.5.2
function _GetExSvrMailboxCount
{
param($Mailboxes,$ExchangeServer,$Databases)
# The following *should* work, but it doesn't. Apparently, ServerName is not always returned correctly which may be the cause of
# reports of counts being incorrect
#([array]($Mailboxes | Where {$_.ServerName -eq $ExchangeServer.Name})).Count
# ..So as a workaround, I'm going to check what databases are assigned to each server and then get the mailbox counts on a per-
# database basis and return the resulting total. As we already have this information resident in memory it should be cheap, just
# not as quick.
$MailboxCount = 0
foreach ($Database in [array]($Databases | Where {$_.Server -eq $ExchangeServer.Name}))
{
$MailboxCount+=([array]($Mailboxes | Where {$_.Database -eq $Database.Identity})).Count
}
$MailboxCount
}
# Sub-Function to Get Exchange Server information
function _GetExSvr
{
param($E2010,$ExchangeServer,$Mailboxes,$Databases)
# Set Basic Variables
$MailboxCount = 0
$RollupLevel = 0
$RollupVersion = ""
# Get WMI Information
$tWMI = Get-WmiObject Win32_OperatingSystem -ComputerName $ExchangeServer.Name -ErrorAction SilentlyContinue
if ($tWMI)
{
$OSVersion = $tWMI.Caption.Replace("(R)","").Replace("Microsoft ","").Replace("Enterprise","Ent").Replace("Standard","Std").Replace(" Edition","")
$OSServicePack = $tWMI.CSDVersion
$RealName = $tWMI.CSName.ToUpper()
} else {
Write-Warning "Cannot detect OS information via WMI for $($ExchangeServer.Name)"
$OSVersion = "N/A"
$OSServicePack = "N/A"
$RealName = $ExchangeServer.Name.ToUpper()
}
$tWMI=Get-WmiObject -query "Select * from Win32_Volume" -ComputerName $ExchangeServer.Name -ErrorAction SilentlyContinue
if ($tWMI)
{
$Disks=$tWMI | Select Name,Capacity,FreeSpace | Sort-Object -Property Name
} else {
Write-Warning "Cannot detect OS information via WMI for $($ExchangeServer.Name)"
$Disks=$null
}
# Get Exchange Version
if ($ExchangeServer.AdminDisplayVersion.Major -eq 6)
{
$ExchangeMajorVersion = "$($ExchangeServer.AdminDisplayVersion.Major).$($ExchangeServer.AdminDisplayVersion.Minor)"
$ExchangeSPLevel = $ExchangeServer.AdminDisplayVersion.FilePatchLevelDescription.Replace("Service Pack ","")
} else {
$ExchangeMajorVersion = $ExchangeServer.AdminDisplayVersion.Major
$ExchangeSPLevel = $ExchangeServer.AdminDisplayVersion.Minor
}
# Exchange 2007+
if ($ExchangeMajorVersion -ge 8)
{
# Get Roles
$MailboxStatistics=$null
[array]$Roles = $ExchangeServer.ServerRole.ToString().Replace(" ","").Split(",");
if ($Roles -contains "Mailbox")
{
$MailboxCount = _GetExSvrMailboxCount -Mailboxes $Mailboxes -ExchangeServer $ExchangeServer -Databases $Databases
if ($ExchangeServer.Name.ToUpper() -ne $RealName)
{
$Roles = [array]($Roles | Where {$_ -ne "Mailbox"})
$Roles += "ClusteredMailbox"
}
# Get Mailbox Statistics the normal way, return in a consitent format
$MailboxStatistics = Get-MailboxStatistics -Server $ExchangeServer | Select DisplayName,@{Name="TotalItemSizeB";Expression={$_.TotalItemSize.Value.ToBytes()}},@{Name="TotalDeletedItemSizeB";Expression={$_.TotalDeletedItemSize.Value.ToBytes()}},Database
}
# Rollup Level / Versions (Thanks to Bhargav Shukla http://bit.ly/msxGIJ)
if ($ExchangeMajorVersion -eq 14)
{
$RegKey="SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\S-1-5-18\\Products\\AE1D439464EB1B8488741FFA028E291C\\Patches"
} else {
$RegKey="SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\S-1-5-18\\Products\\461C2B4266EDEF444B864AD6D9E5B613\\Patches"
}
$RemoteRegistry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $ExchangeServer.Name);
if ($RemoteRegistry)
{
$RUKeys= $RemoteRegistry.OpenSubKey($RegKey).GetSubKeyNames() | ForEach {"$RegKey\\$_"}
if ($RUKeys)
{
[array]($RUKeys | %{$RemoteRegistry.OpenSubKey($_).getvalue("DisplayName")}) | %{
if ($_ -like "Update Rollup *")
{
$tRU = $_.Split(" ")[2]
if ($tRU -like "*-*") { $tRUV=$tRU.Split("-")[1]; $tRU=$tRU.Split("-")[0] } else { $tRUV="" }
if ($tRU -ge $RollupLevel) { $RollupLevel=$tRU; $RollupVersion=$tRUV }
}
}
}
} else {
Write-Warning "Cannot detect Rollup Version via Remote Registry for $($ExchangeServer.Name)"
}
}
# Exchange 2003
if ($ExchangeMajorVersion -eq 6.5)
{
# Mailbox Count
$MailboxCount = _GetExSvrMailboxCount -Mailboxes $Mailboxes -ExchangeServer $ExchangeServer -Databases $Databases
# Get Role via WMI
$tWMI = Get-WMIObject Exchange_Server -Namespace "root\microsoftexchangev2" -Computername $ExchangeServer.Name -Filter "Name='$($ExchangeServer.Name)'"
if ($tWMI)
{
if ($tWMI.IsFrontEndServer) { $Roles=@("FE") } else { $Roles=@("BE") }
} else {
Write-Warning "Cannot detect Front End/Back End Server information via WMI for $($ExchangeServer.Name)"
$Roles+="Unknown"
}
# Get Mailbox Statistics using WMI, return in a consistent format
$tWMI = Get-WMIObject -class Exchange_Mailbox -Namespace ROOT\MicrosoftExchangev2 -ComputerName $ExchangeServer.Name -Filter ("ServerName='$($ExchangeServer.Name)'")
if ($tWMI)
{
$MailboxStatistics = $tWMI | Select @{Name="DisplayName";Expression={$_.MailboxDisplayName}},@{Name="TotalItemSizeB";Expression={$_.Size}},@{Name="TotalDeletedItemSizeB";Expression={$_.DeletedMessageSizeExtended }},@{Name="Database";Expression={((get-mailboxdatabase -Identity "$($_.ServerName)\$($_.StorageGroupName)\$($_.StoreName)").identity)}}
} else {
Write-Warning "Cannot retrieve Mailbox Statistics via WMI for $($ExchangeServer.Name)"
$MailboxStatistics = $null
}
}
# Exchange 2000
if ($ExchangeMajorVersion -eq "6.0")
{
# Mailbox Count
$MailboxCount = _GetExSvrMailboxCount -Mailboxes $Mailboxes -ExchangeServer $ExchangeServer -Databases $Databases
# Get Role via ADSI
$tADSI=[ADSI]"LDAP://$($ExchangeServer.OriginatingServer)/$($ExchangeServer.DistinguishedName)"
if ($tADSI)
{
if ($tADSI.ServerRole -eq 1) { $Roles=@("FE") } else { $Roles=@("BE") }
} else {
Write-Warning "Cannot detect Front End/Back End Server information via ADSI for $($ExchangeServer.Name)"
$Roles+="Unknown"
}
$MailboxStatistics = $null
}
# Return Hashtable
@{Name = $ExchangeServer.Name.ToUpper()
RealName = $RealName
ExchangeMajorVersion = $ExchangeMajorVersion
ExchangeSPLevel = $ExchangeSPLevel
Edition = $ExchangeServer.Edition
Mailboxes = $MailboxCount
OSVersion = $OSVersion;
OSServicePack = $OSServicePack
Roles = $Roles
RollupLevel = $RollupLevel
RollupVersion = $RollupVersion
Site = $ExchangeServer.Site.Name
MailboxStatistics = $MailboxStatistics
Disks = $Disks
}
}
# Sub Function to Get Totals by Version
function _TotalsByVersion
{
param($ExchangeEnvironment)
$TotalMailboxesByVersion=@{}
if ($ExchangeEnvironment.Sites)
{
foreach ($Site in $ExchangeEnvironment.Sites.GetEnumerator())
{
foreach ($Server in $Site.Value)
{
if (!$TotalMailboxesByVersion["$($Server.ExchangeMajorVersion).$($Server.ExchangeSPLevel)"])
{
$TotalMailboxesByVersion.Add("$($Server.ExchangeMajorVersion).$($Server.ExchangeSPLevel)",@{ServerCount=1;MailboxCount=$Server.Mailboxes})
} else {
$TotalMailboxesByVersion["$($Server.ExchangeMajorVersion).$($Server.ExchangeSPLevel)"].ServerCount++
$TotalMailboxesByVersion["$($Server.ExchangeMajorVersion).$($Server.ExchangeSPLevel)"].MailboxCount+=$Server.Mailboxes
}
}
}
}
if ($ExchangeEnvironment.Pre2007)
{
foreach ($FakeSite in $ExchangeEnvironment.Pre2007.GetEnumerator())
{
foreach ($Server in $FakeSite.Value)
{
if (!$TotalMailboxesByVersion["$($Server.ExchangeMajorVersion).$($Server.ExchangeSPLevel)"])
{
$TotalMailboxesByVersion.Add("$($Server.ExchangeMajorVersion).$($Server.ExchangeSPLevel)",@{ServerCount=1;MailboxCount=$Server.Mailboxes})
} else {
$TotalMailboxesByVersion["$($Server.ExchangeMajorVersion).$($Server.ExchangeSPLevel)"].ServerCount++
$TotalMailboxesByVersion["$($Server.ExchangeMajorVersion).$($Server.ExchangeSPLevel)"].MailboxCount+=$Server.Mailboxes
}
}
}
}
$TotalMailboxesByVersion
}
# Sub Function to Get Totals by Role
function _TotalsByRole
{
param($ExchangeEnvironment)
# Add Roles We Always Show
$TotalServersByRole=@{"ClientAccess" = 0
"HubTransport" = 0
"UnifiedMessaging" = 0
"Mailbox" = 0
"Edge" = 0
}
if ($ExchangeEnvironment.Sites)
{
foreach ($Site in $ExchangeEnvironment.Sites.GetEnumerator())
{
foreach ($Server in $Site.Value)
{
foreach ($Role in $Server.Roles)
{
if ($TotalServersByRole[$Role] -eq $null)
{
$TotalServersByRole.Add($Role,1)
} else {
$TotalServersByRole[$Role]++
}
}
}
}
}
if ($ExchangeEnvironment.Pre2007["Pre 2007 Servers"])
{
foreach ($Server in $ExchangeEnvironment.Pre2007["Pre 2007 Servers"])
{
foreach ($Role in $Server.Roles)
{
if ($TotalServersByRole[$Role] -eq $null)
{
$TotalServersByRole.Add($Role,1)
} else {
$TotalServersByRole[$Role]++
}
}
}
}
$TotalServersByRole
}
# Sub Function to return HTML Table for Sites/Pre 2007
function _GetOverview
{
param($Servers,$ExchangeEnvironment,$ExRoleStrings,$Pre2007=$False)
if ($Pre2007)
{
$BGColHeader="#880099"
$BGColSubHeader="#8800CC"
$Prefix=""
} else {
$BGColHeader="#000099"
$BGColSubHeader="#0000FF"
$Prefix="Site:"
}
$Output="<table border=""0"" cellpadding=""3"" width=""100%"" style=""font-size:8pt;font-family:Arial,sans-serif"">
<col width=""20%""><col width=""20%"">
<colgroup width=""25%"">";
#$Output+="<col width=""4%""><col width=""4%""><col width=""3%""><col width=""3%""><col width=""3%""><col width=""3%"">";
$ExchangeEnvironment.TotalServersByRole.GetEnumerator()|Sort Name| %{$Output+="<col width=""3%"">"}
$Output+="</colgroup><col width=""20%""><col width=""20%"">
<tr bgcolor=""$($BGColHeader)""><th><font color=""#ffffff"">$($Prefix) $($Servers.Key)</font></th>
<th><font color=""#ffffff"">Exchange Version</font></th>
<th colspan=""$($ExchangeEnvironment.TotalServersByRole.Count)""><font color=""#ffffff"">Roles Installed</font></th>
<th><font color=""#ffffff"">OS Version</font></th>
<th><font color=""#ffffff"">OS Service Pack</font></th></tr>"
$TotalMailboxes=0
$Servers.Value | %{$TotalMailboxes += $_.Mailboxes}
$Output+="<tr bgcolor=""$($BGColSubHeader)""><th><font color=""#ffffff"">Mailboxes: $($TotalMailboxes)</font></th><th></th>"
$ExchangeEnvironment.TotalServersByRole.GetEnumerator()|Sort Name| %{$Output+="<th><font color=""#ffffff"">$($ExRoleStrings[$_.Key].Short)</font></th>"}
$Output+="<th></th><th></th></tr>"
$AlternateRow=0
foreach ($Server in $Servers.Value)
{
$Output+="<tr "
if ($AlternateRow)
{
$Output+=" style=""background-color:#dddddd"""
$AlternateRow=0
} else
{
$AlternateRow=1
}
$Output+="><td>$($Server.Name)"
if ($Server.RealName -ne $Server.Name)
{
$Output+=" ($($Server.RealName))"
}
$Output+="</td>
<td>$($ExVersionStrings["$($Server.ExchangeMajorVersion).$($Server.ExchangeSPLevel)"].Long)"
if ($Server.RollupLevel -gt 0)
{
$Output+=" UR$($Server.RollupLevel)"
if ($Server.RollupVersion)
{
$Output+=" $($Server.RollupVersion)"
}
}
$Output+="</td>"
$ExchangeEnvironment.TotalServersByRole.GetEnumerator()|Sort Name| %{
$Output+="<td"
if ($Server.Roles -contains $_.Key)
{
$Output+=" align=""center"" style=""background-color:#00FF00"""
}
$Output+=">"
if (($_.Key -eq "ClusteredMailbox" -or $_.Key -eq "Mailbox" -or $_.Key -eq "BE") -and $Server.Roles -contains $_.Key)
{
$Output+=$Server.Mailboxes
}
}
$Output+="<td>$($Server.OSVersion)</td><td>$($Server.OSServicePack)</td></tr>";
}
$Output+="<tr></tr>
</table><br />"
$Output
}
# Sub Function to return HTML Table for Databases
function _GetDBTable
{
param($Databases)
# Only Show Archive Mailbox Columns, Backup Columns and Circ Logging if at least one DB has an Archive mailbox, backed up or Cir Log enabled.
$ShowArchiveDBs=$False
$ShowLastFullBackup=$False
$ShowCircularLogging=$False
$ShowStorageGroups=$False
$ShowCopies=$False
$ShowFreeDatabaseSpace=$False
$ShowFreeLogDiskSpace=$False
foreach ($Database in $Databases)
{
if ($Database.ArchiveMailboxCount -gt 0)
{
$ShowArchiveDBs=$True
}
if ($Database.LastFullBackup -ne "Not Available")
{
$ShowLastFullBackup=$True
}
if ($Database.CircularLoggingEnabled -eq "Yes")
{
$ShowCircularLogging=$True
}
if ($Database.StorageGroup)
{
$ShowStorageGroups=$True
}
if ($Database.CopyCount -gt 0)
{
$ShowCopies=$True
}
if ($Database.FreeDatabaseDiskSpace -ne $null)
{
$ShowFreeDatabaseSpace=$true
}
if ($Database.FreeLogDiskSpace -ne $null)
{
$ShowFreeLogDiskSpace=$true
}
}
$Output="<table border=""0"" cellpadding=""3"" width=""100%"" style=""font-size:8pt;font-family:Arial,sans-serif"">
<tr align=""center"" bgcolor=""#FFD700"">
<th>Server</th>"
if ($ShowStorageGroups)
{
$Output+="<th>Storage Group</th>"
}
$Output+="<th>Database Name</th>
<th>Mailboxes</th>
<th>Av. Mailbox Size</th>"
if ($ShowArchiveDBs)
{
$Output+="<th>Archive MBs</th><th>Av. Archive Size</th>"
}
$Output+="<th>DB Size</th><th>DB Whitespace</th>"
if ($ShowFreeDatabaseSpace)
{
$Output+="<th>Database Disk Free</th>"
}
if ($ShowFreeLogDiskSpace)
{
$Output+="<th>Log Disk Free</th>"
}
if ($ShowLastFullBackup)
{
$Output+="<th>Last Full Backup</th>"
}
if ($ShowCircularLogging)
{
$Output+="<th>Circular Logging</th>"
}
if ($ShowCopies)
{
$Output+="<th>Copies (n)</th>"
}
$Output+="</tr>"
$AlternateRow=0;
foreach ($Database in $Databases)
{
$Output+="<tr"
if ($AlternateRow)
{
$Output+=" style=""background-color:#dddddd"""
$AlternateRow=0
} else
{
$AlternateRow=1
}
$Output+="><td>$($Database.ActiveOwner)</td>"
if ($ShowStorageGroups)
{
$Output+="<td>$($Database.StorageGroup)</td>"
}
$Output+="<td>$($Database.Name)</td>
<td align=""center"">$($Database.MailboxCount)</td>
<td align=""center"">$("{0:N2}" -f ($Database.MailboxAverageSize/1MB)) MB</td>"
if ($ShowArchiveDBs)
{
$Output+="<td align=""center"">$($Database.ArchiveMailboxCount)</td>
<td align=""center"">$("{0:N2}" -f ($Database.ArchiveAverageSize/1MB)) MB</td>";
}
$Output+="<td align=""center"">$("{0:N2}" -f ($Database.Size/1GB)) GB </td>
<td align=""center"">$("{0:N2}" -f ($Database.Whitespace/1GB)) GB</td>";
if ($ShowFreeDatabaseSpace)
{
$Output+="<td align=""center"">$("{0:N1}" -f $Database.FreeDatabaseDiskSpace)%</td>"
}
if ($ShowFreeLogDiskSpace)
{
$Output+="<td align=""center"">$("{0:N1}" -f $Database.FreeLogDiskSpace)%</td>"
}
if ($ShowLastFullBackup)
{
$Output+="<td align=""center"">$($Database.LastFullBackup)</td>";
}
if ($ShowCircularLogging)
{
$Output+="<td align=""center"">$($Database.CircularLoggingEnabled)</td>";
}
if ($ShowCopies)
{
$Output+="<td>$($Database.Copies|%{$_}) ($($Database.CopyCount))</td>"
}
$Output+="</tr>";
}
$Output+="</table><br />"
$Output
}
# Sub Function to neatly update progress
function _UpProg1
{
param($PercentComplete,$Status,$Stage)
$TotalStages=5
Write-Progress -id 1 -activity "Get-ExchangeEnvironmentReport" -status $Status -percentComplete (($PercentComplete/$TotalStages)+(1/$TotalStages*$Stage*100))
}
# 1. Initial Startup
# 1.0 Check Powershell Version
if ((Get-Host).Version.Major -eq 1)
{
throw "Powershell Version 1 not supported";
}
# 1.1 Check Exchange Management Shell, attempt to load
if (!(Get-Command Get-ExchangeServer -ErrorAction SilentlyContinue))
{
if (Test-Path "C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1")
{
. 'C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1'
Connect-ExchangeServer -auto
} elseif (Test-Path "C:\Program Files\Microsoft\Exchange Server\bin\Exchange.ps1") {
Add-PSSnapIn Microsoft.Exchange.Management.PowerShell.Admin
.'C:\Program Files\Microsoft\Exchange Server\bin\Exchange.ps1'
} else {
throw "Exchange Management Shell cannot be loaded"
}
}
# 1.2 Check if -SendMail parameter set and if so check -MailFrom, -MailTo and -MailServer are set
if ($SendMail)
{
if (!$MailFrom -or !$MailTo -or !$MailServer)
{
throw "If -SendMail specified, you must also specify -MailFrom, -MailTo and -MailServer"
}
}
# 1.3 Check Exchange Management Shell Version
if ((Get-PSSnapin -Name Microsoft.Exchange.Management.PowerShell.Admin -ErrorAction SilentlyContinue))
{
$E2010 = $false;
if (Get-ExchangeServer | Where {$_.AdminDisplayVersion.Major -eq 14})
{
Write-Warning "Exchange 2010 detected. You'll get better results if you run this script from an Exchange 2010 management shell"
}
}else{
$E2010 = $true
}
# 1.4 Check view entire forest if set (by default, true)
if ($E2010)
{
Set-ADServerSettings -ViewEntireForest:$ViewEntireForest
} else {
$global:AdminSessionADSettings.ViewEntireForest = $ViewEntireForest
}
# 1.5 Initial Variables
# 1.5.1 Hashtable to update with environment data
$ExchangeEnvironment = @{Sites = @{}
Pre2007 = @{}
Servers = @{}
DAGs = @()
NonDAGDatabases = @()
}
# 1.5.7 Exchange Major Version String Mapping
$ExMajorVersionStrings = @{"6.0" = @{Long="Exchange 2000";Short="E2000"}
"6.5" = @{Long="Exchange 2003";Short="E2003"}
"8" = @{Long="Exchange 2007";Short="E2007"}
"14" = @{Long="Exchange 2010";Short="E2010"}}
# 1.5.8 Exchange Service Pack String Mapping
$ExSPLevelStrings = @{"0" = "RTM"
"1" = "SP1"
"2" = "SP2"
"3" = "SP3"
"4" = "SP4"}
# 1.5.9 Populate Full Mapping using above info
$ExVersionStrings = @{}
foreach ($Major in $ExMajorVersionStrings.GetEnumerator())
{
foreach ($Minor in $ExSPLevelStrings.GetEnumerator())
{
$ExVersionStrings.Add("$($Major.Key).$($Minor.Key)",@{Long="$($Major.Value.Long) $($Minor.Value)";Short="$($Major.Value.Short)$($Minor.Value)"})
}
}
# 1.5.10 Exchange Role String Mapping
$ExRoleStrings = @{"ClusteredMailbox" = @{Short="ClusMBX";Long="CCR/SCC Clustered Mailbox"}
"Mailbox" = @{Short="MBX";Long="Mailbox"}
"ClientAccess" = @{Short="CAS";Long="Client Access"}
"HubTransport" = @{Short="HUB";Long="Hub Transport"}
"UnifiedMessaging" = @{Short="UM";Long="Unified Messaging"}
"Edge" = @{Short="EDGE";Long="Edge Transport"}
"FE" = @{Short="FE";Long="Front End"}
"BE" = @{Short="BE";Long="Back End"}
"Unknown" = @{Short="Unknown";Long="Unknown"}}
# 2 Get Relevant Exchange Information Up-Front
# 2.1 Get Server, Exchange and Mailbox Information
_UpProg1 1 "Getting Exchange Server List" 1
$ExchangeServers = [array](Get-ExchangeServer $ServerFilter)
if (!$ExchangeServers)
{
throw "No Exchange Servers matched by -ServerFilter ""$($ServerFilter)"""
}
_UpProg1 10 "Getting Mailboxes" 1
$Mailboxes = [array](Get-Mailbox -ResultSize Unlimited) | Where {$_.Server -like $ServerFilter}
if ($E2010)
{
_UpProg1 60 "Getting Archive Mailboxes" 1
$ArchiveMailboxes = [array](Get-Mailbox -Archive -ResultSize Unlimited) | Where {$_.Server -like $ServerFilter}
_UpProg1 90 "Getting Databases" 1
$Databases = [array](Get-MailboxDatabase -IncludePreExchange2010 -Status) | Where {$_.Server -like $ServerFilter}
$DAGs = [array](Get-DatabaseAvailabilityGroup) | Where {$_.Servers -like $ServerFilter}
} else {
$ArchiveMailboxes = $null
$ArchiveMailboxStats = $null
$DAGs = $null
_UpProg1 90 "Getting Databases" 1
$Databases = [array](Get-MailboxDatabase -IncludePreExchange2007 -Status) | Where {$_.Server -like $ServerFilter}
}
# 2.3 Populate Information we know
$ExchangeEnvironment.Add("TotalMailboxes",$Mailboxes.Count);
# 3 Process High-Level Exchange Information
# 3.1 Collect Exchange Server Information
for ($i=0; $i -lt $ExchangeServers.Count; $i++)
{
_UpProg1 ($i/$ExchangeServers.Count*100) "Getting Exchange Server Information" 2
# Get Exchange Info
$ExSvr = _GetExSvr -E2010 $E2010 -ExchangeServer $ExchangeServers[$i] -Mailboxes $Mailboxes -Databases $Databases
# Add to site or pre-Exchange 2007 list
if ($ExSvr.Site)
{
# Exchange 2007 or higher
if (!$ExchangeEnvironment.Sites[$ExSvr.Site])
{
$ExchangeEnvironment.Sites.Add($ExSvr.Site,@($ExSvr))
} else {
$ExchangeEnvironment.Sites[$ExSvr.Site]+=$ExSvr
}
} else {
# Exchange 2003 or lower
if (!$ExchangeEnvironment.Pre2007["Pre 2007 Servers"])
{
$ExchangeEnvironment.Pre2007.Add("Pre 2007 Servers",@($ExSvr))
} else {
$ExchangeEnvironment.Pre2007["Pre 2007 Servers"]+=$ExSvr
}
}
# Add to Servers List
$ExchangeEnvironment.Servers.Add($ExSvr.Name,$ExSvr)
}
# 3.2 Calculate Environment Totals for Version/Role using collected data
_UpProg1 1 "Getting Totals" 3
$ExchangeEnvironment.Add("TotalMailboxesByVersion",(_TotalsByVersion -ExchangeEnvironment $ExchangeEnvironment))
$ExchangeEnvironment.Add("TotalServersByRole",(_TotalsByRole -ExchangeEnvironment $ExchangeEnvironment))
# 3.4 Populate Environment DAGs
_UpProg1 5 "Getting DAG Info" 3
if ($DAGs)
{
foreach($DAG in $DAGs)
{
$ExchangeEnvironment.DAGs+=(_GetDAG -DAG $DAG)
}
}
# 3.5 Get Database information
_UpProg1 60 "Getting Database Info" 3
for ($i=0; $i -lt $Databases.Count; $i++)
{
$Database = _GetDB -Database $Databases[$i] -ExchangeEnvironment $ExchangeEnvironment -Mailboxes $Mailboxes -ArchiveMailboxes $ArchiveMailboxes -E2010 $E2010
$DAGDB = $false
for ($j=0; $j -lt $ExchangeEnvironment.DAGs.Count; $j++)
{
if ($ExchangeEnvironment.DAGs[$j].Members -contains $Database.ActiveOwner)
{
$DAGDB=$true
$ExchangeEnvironment.DAGs[$j].Databases += $Database
}
}
if (!$DAGDB)
{
$ExchangeEnvironment.NonDAGDatabases += $Database
}
}
# 4 Write Information
_UpProg1 5 "Writing HTML Report Header" 4
# Header
$Output="<html>
<body>
<font size=""1"" face=""Arial,sans-serif"">
<h3 align=""center"">Exchange Environment Report</h3>
<h5 align=""center"">Generated $((Get-Date).ToString())</h5>
</font>
<table border=""0"" cellpadding=""3"" style=""font-size:8pt;font-family:Arial,sans-serif"">
<tr bgcolor=""#009900"">
<th colspan=""$($ExchangeEnvironment.TotalMailboxesByVersion.Count)""><font color=""#ffffff"">Total Servers:</font></th>
<th colspan=""$($ExchangeEnvironment.TotalMailboxesByVersion.Count+1)""><font color=""#ffffff"">Total Mailboxes:</font></th>
<th colspan=""$($ExchangeEnvironment.TotalServersByRole.Count)""><font color=""#ffffff"">Total Roles:</font></th></tr>
<tr bgcolor=""#00CC00"">"
# Show Column Headings based on the Exchange versions we have
$ExchangeEnvironment.TotalMailboxesByVersion.GetEnumerator()|Sort Name| %{$Output+="<th>$($ExVersionStrings[$_.Key].Short)</th>"}
$ExchangeEnvironment.TotalMailboxesByVersion.GetEnumerator()|Sort Name| %{$Output+="<th>$($ExVersionStrings[$_.Key].Short)</th>"}
$Output+="<th>Org</th>"
$ExchangeEnvironment.TotalServersByRole.GetEnumerator()|Sort Name| %{$Output+="<th>$($ExRoleStrings[$_.Key].Short)</th>"}
$Output+="<tr>"
$Output+="<tr align=""center"" bgcolor=""#dddddd"">"
$ExchangeEnvironment.TotalMailboxesByVersion.GetEnumerator()|Sort Name| %{$Output+="<td>$($_.Value.ServerCount)</td>" }
$ExchangeEnvironment.TotalMailboxesByVersion.GetEnumerator()|Sort Name| %{$Output+="<td>$($_.Value.MailboxCount)</td>" }
$Output+="<td>$($ExchangeEnvironment.TotalMailboxes)</td>"
$ExchangeEnvironment.TotalServersByRole.GetEnumerator()|Sort Name| %{$Output+="<td>$($_.Value)</td>"}
$Output+="</tr><tr><tr></table><br>"
# Sites and Servers
_UpProg1 20 "Writing HTML Site Information" 4
foreach ($Site in $ExchangeEnvironment.Sites.GetEnumerator())
{
$Output+=_GetOverview -Servers $Site -ExchangeEnvironment $ExchangeEnvironment -ExRoleStrings $ExRoleStrings
}
_UpProg1 40 "Writing HTML Pre-2007 Information" 4
foreach ($FakeSite in $ExchangeEnvironment.Pre2007.GetEnumerator())
{
$Output+=_GetOverview -Servers $FakeSite -ExchangeEnvironment $ExchangeEnvironment -ExRoleStrings $ExRoleStrings -Pre2007:$true
}
_UpProg1 60 "Writing HTML DAG Information" 4
foreach ($DAG in $ExchangeEnvironment.DAGs)
{
if ($DAG.MemberCount -gt 0)
{
# Database Availability Group Header
$Output+="<table border=""0"" cellpadding=""3"" width=""100%"" style=""font-size:8pt;font-family:Arial,sans-serif"">
<col width=""20%""><col width=""10%""><col width=""70%"">
<tr align=""center"" bgcolor=""#FF8000 ""><th>Database Availability Group Name</th><th>Member Count</th>
<th>Database Availability Group Members</th></tr>
<tr><td>$($DAG.Name)</td><td align=""center"">
$($DAG.MemberCount)</td><td>"
$DAG.Members | % { $Output+="$($_) " }
$Output+="</td></tr></table>"
# Get Table HTML
$Output+=_GetDBTable -Databases $DAG.Databases
}
}
if ($ExchangeEnvironment.NonDAGDatabases.Count)
{
_UpProg1 80 "Writing HTML Non-DAG Database Information" 4
$Output+="<table border=""0"" cellpadding=""3"" width=""100%"" style=""font-size:8pt;font-family:Arial,sans-serif"">
<tr bgcolor=""#FF8000""><th>Mailbox Databases (Non-DAG)</th></table>"
$Output+=_GetDBTable -Databases $ExchangeEnvironment.NonDAGDatabases
}
# End
_UpProg1 90 "Finishing off.." 4
$Output+="</body></html>";
$Output | Out-File $HTMLReport
if ($SendMail)
{
_UpProg1 95 "Sending mail message.." 4
Send-MailMessage -Attachments $HTMLReport -To $MailTo -From $MailFrom -Subject "Exchange Environment Report" -BodyAsHtml $Output -SmtpServer $MailServer
}
if ($ScheduleAs)
{
_UpProg1 99 "Attempting to Schedule Task.." 4
$dir=(split-path -parent $myinvocation.mycommand.definition)
$params="-HTMLReport $HTMLReport"
if ($SendMail)
{
$params+=' -SendMail:$true'
$params+=" -MailFrom:$MailFrom -MailTo:$MailTo -MailServer:$MailServer"
}
$task = "powershell -c \""pushd $dir; $($myinvocation.mycommand.definition) $params\"""
Write-Output "Attempting to schedule task as $($ScheduleAs)..."
Write-Output "Task to schedule: $($task)"
schtasks /Create /RU $ScheduleAs /RP /SC DAILY /ST 22:00 /TN EER /TR $task
}
Download Get-ExchangeEnvironmentReport.ps1
Looking for the previous version? Download version 1.1 here
Using the Exchange 2010 SP1 Mailbox Export features for Mass Exports to PST files
In Exchange 2007 SP1 thru to Exchange 2010 RTM, the Export-Mailbox command was the replacement for the once-familiar ExMerge utility when it came to exporting mailboxes to PST files.
The main problem with Export-Mailbox for most Exchange administrators is the requirement for Outlook – either on a 32-bit machine with Management Tools for Exchange 2007, or on a 64-bit machine for Exchange 2010. All in all, it wasn’t ideal and certainly didn’t facilitate scripted mailbox exports.
Thankfully, with Exchange 2010 SP1, Export-Mailbox is going the way of the dodo and new cmdlets for Mailbox imports and exports are available. Just like the New-MoveRequest cmdlet, the new import/export command use the Mailbox Replication Service to perform the move via one of the Client Access Servers giving performance benefits, such as ensuring the PST transfer doesn’t have to go via the machine with the Exchange Management Tools/Outlook installed, as was the case previously.
The main aim of this post is to give you an overview of how to use the new mailbox export cmdlets, and then show you how to put them to practical use, both at the command line and with a scheduled task for brick-level backups.
Getting it set up
The basic requirements for using the new feature are pretty straightforward. You need to use an account that’s a member of the organisational management groups, and have the “Mailbox Import Export” role assignment assigned to you or a role group you’re a member of. As the export is done at a CAS server (and if you’ve multiple CAS servers you can’t specify which one in each site will be used) you can’t specify a local drive letter and path – you must specify a UNC path to a network share that the “Exchange Trusted Subsystem” group has read/write access to.
Step One
Create a share on a server, and grant Exchange Trusted Subsystem read/write permission. In this example I’m using a share called Exports on a test server called Azua in my lab environment:
Step Two
Next, you’ll need to grant a user, or group, the Mailbox Import Export role assignment. You can do this using the Exchange Management shell with a single command. In this example, I’m granting my lab domain’s Administrator user the role assignment:
After you’ve done this, close and re-open the Exchange Management shell, and you’re ready to go!
Exporting a Mailbox
At it’s simplest, use the New-MailboxExportRequest command with the –Mailbox parameter, to specify the mailbox to export along with the –FilePath parameter, to specify the PST file to create and export data to, e.g:
In addition, there are some other useful options – such as –BatchName, which allows grouping of requests together, and –ContentFilter, which allows only certain content to be exported to the PST – useful for discovery purposes. As usual, use the Get-Help New-MailboxExportRequest –detailed command to review the full plethora of options.
After submission of your requests, you can check progress, including the percentage complete, with the two Get-MailboxExportRequest and the Get-MailboxExportRequestStatistics commands. Pipe the former into the latter to get a listing:
After the requests complete, you can remove the requests in a similar fashion, using the Remove-MailboxExportRequest command:
Performing mass exports
One benefit of Powershell is it’s very easy to put together commands enabling mass-exports of PST data with only a few commands. If you really wanted to, you could even use a Powershell script as a secondary brick-level backup!
The Basics
So to check out how to do this, let’s look at it’s simplest – backing up all the mailboxes (assuming it’s a full Exchange 2010 environment) to a single share:
In the above example, we’re simply performing a for-each loop through each mailbox and creating a new Mailbox Export Request, using the alias to build the name for the PST.
But – what if we’re in a mixed environment, and only want to target the Exchange 2010 mailboxes?
In this example above, now, we’ve added a clause to only select the mailboxes where the Exchange Major Build is 14 – Exchange 2010. Simple!
Moving on from such wide-targeting, you may want to target just a pre-defined list, using a CSV file. To do this, simply create a CSV file with the column “Alias”, and list the Mailbox alias fields you wish to export. Then, using the Import-CSV command we can use this CSV file to create the requests:
Performing Mass Exports as a scheduled task
Now you’ve seen the basics of how easy it is to perform mass mailbox exports using the New-MailboxExportRequest command, I’ll finish off with a final example showing how to use this mass export feature as part of a scheduled task.
This script is aimed at backing up all the mailboxes on a single database, or a single server. After creating the requests, it waits for the requests to complete then, if you’ve specified a report directory, it will write reports showing completed and incomplete (i.e. failed!) requests. Finally it removes the requests it created.
To use the script, you need to alter the config section and specify either a server or a database, a share to export to, a share to write a report to after the process has completed and you can choose whether to remove each mailbox’s associated PST file or leave as-is at each export – merging the contents.
You’ll see the content of the script below and at the bottom of the post I’ve zipped it up along with a bootstrap CMD file you could use when setting up a schedule task. As always – use at your own risk and test out in your lab environment first. Happy Exporting!
# Steve Goodman. Use at your own risk!
###############
# Settings #
###############
# Pick ONE of the two below. If you choose both, it will use $Server.
$Server = "server"
$Database = ""
# Share to export mailboxes to. Needs R/W by Exchange Trusted Subsystem
# Must be a UNC path as this is run by the CAS MRS service.
$ExportShare = "\\server\share"
# After each run a report of the exports can be dropped into the directory specified below. (The user that runs this script needs access to this share)
# Must be a UNC path or the full path of a local directory.
$ReportShare = "\\server\share"
# Shall we remove the PST file, if it exists beforehand? (The user that runs this script needs access to the $ExportShare share)
# Valid values: $true or $false
$RemovePSTBeforeExport = $false
###############
# Code #
###############
if ($Server)
{
if (!(Get-ExchangeServer $Server -ErrorAction SilentlyContinue))
{
throw "Exchange Server $Server not found";
}
if (!(Get-MailboxDatabase -Server $Server -ErrorAction SilentlyContinue))
{
throw "Exchange Server $Server does not have mailbox databases";
}
$Mailboxes = Get-Mailbox -Server $Server -ResultSize Unlimited
} elseif ($Database) {
if (!(Get-MailboxDatabase $Database -ErrorAction SilentlyContinue))
{
throw "Mailbox database $Database not found"
}
$Mailboxes = Get-Mailbox -Database $Database
}
if (!$Mailboxes)
{
throw "No mailboxes found on $Server"
}
if (!$Mailboxes.Count)
{
throw "This script does not support a single mailbox export."
}
# Pre-checks done
# Make batch name
$date=Get-Date
$BatchName = "Export_$($date.Year)-$($date.Month)-$($date.Day)_$($date.Hour)-$($date.Minute)-$($date.Second)"
Write-Output "Queuing $($Mailboxes.Count) mailboxes as batch '$($BatchName)'"
# Queue all mailbox export requests
foreach ($Mailbox in $Mailboxes)
{
if ($RemovePSTBeforeExport -eq $true -and (Get-Item "$($ExportShare)\$($Mailbox.Alias).PST" -ErrorAction SilentlyContinue))
{
Remove-Item "$($ExportShare)\$($Mailbox.Alias).PST" -Confirm:$false
}
New-MailboxExportRequest -BatchName $BatchName -Mailbox $Mailbox.Alias -FilePath "$($ExportShare)\$($Mailbox.Alias).PST"
}
Write-Output "Waiting for batch to complete"
# Wait for mailbox export requests to complete
while ((Get-MailboxExportRequest -BatchName $BatchName | Where {$_.Status -eq "Queued" -or $_.Status -eq "InProgress"}))
{
sleep 60
}
# Write reports if required
if ($ReportShare)
{
Write-Output "Writing reports to $($ReportShare)"
$Completed = Get-MailboxExportRequest -BatchName $BatchName | Where {$_.Status -eq "Completed"} | Get-MailboxExportRequestStatistics | Format-List
if ($Completed)
{
$Completed | Out-File -FilePath "$($ReportShare)\$($BatchName)_Completed.txt"
}
$Incomplete = Get-MailboxExportRequest -BatchName $BatchName | Where {$_.Status -ne "Completed"} | Get-MailboxExportRequestStatistics | Format-List
if ($Incomplete)
{
$Incomplete | Out-File -FilePath "$($ReportShare)\$($BatchName)_Incomplete_Report.txt"
}
}
# Remove Requests
Write-Output "Removing requests created as part of batch '$($BatchName)'"
Get-MailboxExportRequest -BatchName $BatchName | Remove-MailboxExportRequest -Confirm:$false
Command file contents:


