Generate Exchange Environment Reports using Powershell


Update 16th September – V1.6.1 – Exchange 2016 support and various bug fixes (see here for more information)

Update 2nd February – V1.5.8 – Exchange 2013 CU and SP support, HTTPS and CAS array names shown, initial Office 365 Hybrid support (see here for more information)

Update 19th January – V1.5.6 – Exchange 2013 support and bug fixes (see here for more information)

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, 2013 and 2016 servers and database availability groups – in particular:

    • Total Servers per Exchange version & service pack
    • Total Mailboxes per Exchange version & service pack, plus Office 365 remote mailboxes
    • Totals for Exchange roles across the environment
    • A site-by-site breakdown for the following:
      • Mailboxes per site
      • HTTPS FQDNs used for Internal, External and SCP URLs
      • CAS array names
      • 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:

.\Get-ExchangeEnvironmentReport -HTMLReport c:\report.html

If you want it to email the results, the follow parameters are available to allow the report to be sent directly from the script:

.\Get-ExchangeEnvironmentReport -HTMLReport c:\report.html -SendMail:$true

As usual, the script is provided to download as-is without any warranties, at the bottom of this post. Comments and suggestions are always welcome.

Download Get-ExchangeEnvironmentReport.ps1



663 thoughts on “Generate Exchange Environment Reports using Powershell

  1. I am not much of a powershell user, just use exchange commands, not many custom scripts. I would like to use this script, but cannot even get past the error unexpected token ‘$(‘ in expression or statement. This is referring to line 521, char 45 as follows:

    $Output+=”$($ExVersionStrings[“$($ <<<< Server.ExchangeMajorVersion).$($Server.ExchangeSPLevel)"].Long)"

    I am no programmer, so I don't know what the syntax should be on this line. What is wrong with the series of characters before Server.ExchangeMajorVersion?

  2. For our Exchange 2013 (transitioning from 2010) environment, I had to change the Get-Mailbox lines to use $_.ServerName instead of $_.Server.

  3. is it possible to add Available drive space on each exchange server. it would be nice to have that in email every morning.

  4. We have exchange globally in 9 distinct regions. The server names are all prefixed with their respective regions, G01 through G09 (ex. g05usexsuya01) We have currently >675 active Databases. How can I configure the tool to only query the G05 servers?

  5. Hi and thanks for the great script!

    When i run this command from Exchange management shell it was succeded to create task and send me an email:

    .\Get-ExchangeEnvironmentReport -HTMLReport c:\report.html -SendMail:$true -ScheduleAs:DOMAIN\user

    But when i run the task manually it says success but did not send any email.
    How can I fix this?


  6. Pingback: 8款免费工具对于Exchange管理员 | 爱七七五八网

  7. Hello,

    Again, thank you a lot for this script!
    This will perhaps be useful to others :

    1) add support for Exchange 2013 latest CU:

    Complete section :

    $ExSPLevelStrings = @{“0” = “RTM”
    “1” = “SP1”
    “2” = “SP2”
    “3” = “SP3”
    “4” = “SP4”
    “CU1” = “CU1”
    “CU2” = “CU2”
    “CU3” = “CU3”
    “CU4” = “CU4”
    “CU5” = “CU5”
    “CU6” = “CU6”
    “CU7” = “CU7”
    “CU8” = “CU8”
    “SP1” = “SP1”
    “SP2” = “SP2”}

    2) Support for scheduled task (manualy created) on Exchange 2013 SP1 CU6 :

    Modify this section replacing the V14 by V15 (done here):

    # 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\V15\bin\RemoteExchange.ps1”)
    . ‘C:\Program Files\Microsoft\Exchange Server\V15\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”

  8. Pingback: Generate Exchange Environment Reports using Powershell | Bret-Tech

  9. Pingback: Check Exchange server health | AMAGSMB

  10. Is it possible to specify specific DAG’s in a large organisation as opposed to it gathering all information?
    Thank you.

  11. does anyone know if 1.5.8 can run from a legacy Exchange 2007 server? Ive run it from an EMS session and get the following:

    [PS] E:\Program Files\Microsoft\Exchange Server\Scripts>.\Get-ExchangeEnvironmentReport.ps1
    Unexpected token ‘and’ in expression or statement.
    At E:\Program Files\Microsoft\Exchange Server\Scripts\Get-ExchangeEnvironmentReport.ps1:16 char:19
    + 2013, 2010 and <<<< to a lesser extent, 2007 and 2003, environment.

  12. Pingback: 8 Free tools For Exchange Admins |

  13. It doesn’t work:

    The property ‘ViewEntireForest’ cannot be found on this object. Verify that the property exists and can be set.
    At C:\scripts\exchange\Get-ExchangeEnvironmentReport.ps1:851 char:2
    + $global:AdminSessionADSettings.ViewEntireForest = $ViewEntireForest
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : PropertyNotFound

  14. Pingback: Generate Exchange Environment Reports using Powershell | CuruIT

  15. Whenever I run this report it shows one of my testing Exchange Servers as Green despite every Exchange related service on it being Disabled. Anyone know why this is and more importantly how I can correct it so that server appears Yellow or Red? Or maybe know how to suppress that server from the report entirely?

    When I turn that server off this report will not complete running until its back on, I had a 4 day gap of no EER’s because the test server was offline and that disturbed my Bosses quite considerably. Thank you in advance for any help or insight you can provide.

  16. I immediately get an error when I try to run the script:
    Unexpected token ‘and’ in expression or statement. At C:\temp\Get-ExchangeEnvironmentReport.ps1:16 char:19 + 2013, 2010 and <<<< to a lesser extent, 2007 and 2003, environment.

  17. Pingback: Инвентаризация ВСЕХ серверов Exchange в организации |

  18. FYI to anyone else noticing the same: Seems to be a bug with DB whitespace and average mailbox size for Exchange 2003 DBs which is most likely due to the switch from KB to MB size reporting in 2007 and higher versions of exchange. I managed to mangle the code to get it in the right ballpark:
    $MailboxItemSizeB = $MailboxItemSizeB * 1000
    $MailboxDeletedItemSizeB = $MailboxDeletedItemSizeB * 1000
    Maybe should be 1024 instead of 1000, not sure. Note that DB whitespace is estimated based on “database size – total of mailbox sizes” and will frequently show way off for EX2003 due to database deduplication feature which was lost in EX2007 (or was it EX2010?).

  19. Thanks a lot for the script , this is like heaven feeling.
    I am very very new to scripting and i would like to know where can i add the exchange server names in the script so i can get the results, i have 3 mbx server and 2 cas .
    If anyone can reply to this it would be great

  20. Can anybody let me know how to set parameter to save report in html. Because when I execute this script it is prompting to manually enter path where to save report file. Thanks.

  21. I’d love it if in a future version you could add thresholds, so that if log disk free is under x percent free it’s highlighted yellow, or under y percent is highlighted red. Same for DB whitespace, and a Last Full Backup that uses something like GetDate-1

  22. Excellent report!

    Tell me please, how i can sort line in table, for example Get-MailboxDatabase | Sort-Object -Property Name

  23. I would like to thank you for your work on this script, it is absolutely fantastic. I ran into a small hiccup when trying to run this on 2013 as we use management servers with just management tools installed where we run the majority of custom scripts. The problem I found with this was that when you check the exchange server version it fails because the management tools server isn’t an actual exchange server.

    $E2010 = $true
    $localserver = get-exchangeserver Env:ComputerName
    $localversion = $localserver.admindisplayversion.major
    if ($localversion -eq 15) { $E2013 = $true }

    I made the following change and I’m submitting it here for anyone else that may run into it and for your review.

    $localserver = get-exchangeserver ((Get-PSSession |?{$_.STate -eq “Opened”}).ComputerName)

    Again thank you very much for your excellent work on this!!

  24. Getting an odd error when trying to use as a scheduled task. The report runs perfectly fine if I run it from PS manually.

    I scheduled the task using the -ScheduleAs flag you provided. Once scheduled, I kick off the scheduled task manually and it doesn’t run. I put the action commands your script schedules into a cmd prompt and get the following:

    powershell -c “pushd C:\Scripts; C:\Scripts\Get-ExchangeEnvironmentReport.ps1 -HTMLReport c:\scripts\reports\report.html -SendMail:$true -Mail”
    Exchange Management Shell cannot be loaded
    At C:\Scripts\Get-ExchangeEnvironmentReport.ps1:808 char:3
    + throw “Exchange Management Shell cannot be loaded”
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OperationStopped: (Exchange Manage…annot be lo
    aded:String) [], RuntimeException
    + FullyQualifiedErrorId : Exchange Management Shell cannot be loaded

    I’m pretty new to PS, so it may something simple I’m missing.

    Any thoughts?

  25. Great report! Any thoughts on adding Public Folder DB information in future? Particularly PF DB size & disk free space etc?

    • I’m looking at adding public folder information as well. Did you get anywhere trying to add it yourself?

  26. Steve,

    Re my earlier bug report, “Cannot write input as there are no more running pipelines”, it is caused by piping in the _GetDB function, on the following line:

    $ArchiveStatistics = [array]($ArchiveMailboxes | Where {$_.ArchiveDatabase -eq $Database.Name} | Get-MailboxStatistics -Archive )

    Replacing the pipes with Foreach and If statements got me past it (I prepended my variables with my initials, ze_):

    $ArchiveStatistics = @()
    Foreach ($ze_ArchiveMailbox in $ArchiveMailboxes) {
    if ($ze_ArchiveMailbox.ArchiveDatabase -eq $Database.Name) {
    $ArchiveStatistics += Get-MailboxStatistics -Identity $ze_ArchiveMailbox -Archive

    Also, in the same function, the script throws two unhandled errors per database for databases that are not mounted. In my case I had two recovery databases, both dismounted.

    The errors (ignore the line numbers, I added some tracing cmdlets, so they won’t match yours):

    You cannot call a method on a null-valued expression.
    At C:\Scripts\Get-ExchangeEnvironmentReport.ps1:229 char:47
    + [long]$Size = $Database.DatabaseSize.ToBytes <<<< ()
    + CategoryInfo : InvalidOperation: (ToBytes:String) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    You cannot call a method on a null-valued expression.
    At C:\Scripts\Get-ExchangeEnvironmentReport.ps1:230 char:65
    + [long]$Whitespace = $Database.AvailableNewMailboxSpace.ToBytes <<<< ()
    + CategoryInfo : InvalidOperation: (ToBytes:String) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    The offending lines are:

    # DB Size / Whitespace Info
    [long]$Size = $Database.DatabaseSize.ToBytes()
    [long]$Whitespace = $Database.AvailableNewMailboxSpace.ToBytes()

    My fix:
    # DB Size / Whitespace Info
    if ($Database.DatabaseSize) {
    [long]$Size = $Database.DatabaseSize.ToBytes()
    else {[long]$Size = 0}
    if ($Database.AvailableNewMailboxSpace) {
    [long]$Whitespace = $Database.AvailableNewMailboxSpace.ToBytes()
    else {[long]$Whitespace = 0}


  27. Steve, the script fails in an Ex2010 SP3 UR5 environment with 1300 users, 10 MB servers (2x DAGs with 4x servers each plus 2x MB servers with local database only), approx. 18 databases. The error:

    Sending data to a remote command failed with the following error message: The WinRM client sent a request to the remote WS-Management service and got a response saying the request was too large. For more information, see the about_Remote_Troubleshooting Help topic.
    + CategoryInfo : OperationStopped: (System.Manageme…pressionSyncJob:PSInvokeExpressionSyncJob) [], PSRemotingTransportException
    + FullyQualifiedErrorId : JobFailure

    Invoke-Command : Cannot write input as there are no more running pipelines
    At C:\Users\user\AppData\Roaming\Microsoft\Exchange\RemotePowerShell\server.domain.local\server.domain.local.psm1:15674 char:29
    + $scriptCmd = { & <<<< $script:InvokeCommand `
    + CategoryInfo : InvalidOperation: (:) [Invoke-Command], PSInvalidOperationException
    + FullyQualifiedErrorId : NoMoreInputWrite,Microsoft.PowerShell.Commands.InvokeCommandCommand

    Any thoughts?


  28. Pingback: #MSExchange 2013 Health Check #Powershell Scripts | Microsoft Exchange for IT Professionals

  29. Steve ,

    This script is the greatest thing ever since sliced bread!!!. Was just wondering is it possible to export it into Excell format?

  30. Pingback: Generate Exchange Environment Report | My Stuff With Windows

  31. Hi Steve,

    Seems like there is still a bug in reporting the latest rollup version for Exchange 2007. It is reporting RU9 for all of my servers although RU13 is installed… seems script is not properly evaluating all \\Patches subkeys properly… goes wrong here, any ideas?

    # Rollup Level / Versions (Thanks to Bhargav Shukla
    if ($ExchangeMajorVersion -ge 14)
    } else {
    $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)”

  32. Seems there is a bug related to log disk free space reporting…

    Simple Exchange 2013 single server using mount points for database and log volumes. Database disk free space is reported correctly for the database volume, but log disk free space is being reported for the mount point host volume rather than the log volume itself.

    Thanks for the script!

  33. Nicely done on the script and thank you Ken for the correction on the 1.5.8 error of this script, working perfectly

  34. Steve,

    Great script but wonder if you could help me out with an issue that has started with it.

    Running the latest version of the script against our Exchange 2010 org and being given an error.

    “You must provide a value for this property.
    (0:Int32) [Get-MailboxDatabase]
    FullyQualifiedErrorId : 15731E13,Microsoft,Exchange,management,SystemConfigurationTasks.GetMailboxDatabase”

    I can run the Get-MailboxDatabase cmdlet without issue.

    Any pointers???

    Thanks buddy!

  35. Any ideas on this?

    Exception calling “OpenRemoteBaseKey” with “2” argument(s): “The network path was not found.

    At C:\Get-ExchangeEnvironmentReport.ps1:345 char:69
    + $RemoteRegistry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey <<<< ('LocalMachine', $ExchangeServer.Name
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException

    Any help would be appreciated.

  36. Great script! Thank you for that. Runs fine on my 2008r2/2010 setup.
    Some extra details would be interesting too: How much of which licenses are needed (standard or enterprise) per database / server.

  37. I’m having the same problem as Ken, error in lines 567 en 568, seems the join has some problems. Using 1.5.8 as a first time user.

  38. Excelent report thanks !
    I just have a comment about white spaces. In my multiple DAG environment, free spaces values should be different for each databases but they are same for each servers. I think there is a mistake here.

  39. Looks like it’s not officially tested with Windows 2012 yet, is that right? I’m running E2K13SP1 on 2012 RTM. The mailbox counts are all zero for each server.

    • is your exchange in another subdomain than your users? I had this issue too before I entered set-adserversettings -viewentireforest $true

      • I found the problem and got it working. Script does not get all data if run from Exchange 2013 server with only management tools installed. It needs to run on at least 2013 with mailbox role. I have CAS\MBX servers and then one admin server with only 2013 Management tools.

  40. Getting an Error on the 1.5.8 version if there is no value for ExtNames for some servers. Fixed this by adding an If clause around the join command. Made the following change:

    Original: $ExtNames = [system.String]::Join(“,”,$ExtNames)

    Updated: If ($ExtNames)
    $ExtNames = [system.String]::Join(“,”,$ExtNames)

Comments are closed.