Generate Exchange Environment Reports using Powershell

image

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 and 2013 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 -MailFrom:you@example.com -MailTo:you@example.com -MailServer:smtp.example.com

Finally, to schedule the report to be generated nightly, execute with your preferred options and add the -ScheduleAs parameter, for example:

.\Get-ExchangeEnvironmentReport -HTMLReport c:\report.html -SendMail:$true -MailFrom:you@example.com -MailTo:you@example.com -MailServer:smtp.example.com -ScheduleAs:DOMAIN\user

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 to download as-is without any warranties, at the bottom of this post. Comments and suggestions are always welcome.

Download Get-ExchangeEnvironmentReport.ps1

Looking for the previous version, 1.5.4? Download it here

 

507 thoughts on “Generate Exchange Environment Reports using Powershell

  1. 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!!

  2. 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 From:email@address.com -MailTo:email@address.com -MailServer:mail.server.com”
    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?

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

  4. 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}
    ================

    Regards,
    Zoltan

  5. 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?

    Thanks
    Zoltan

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

  7. Steve ,

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

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

  9. 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 http://bit.ly/msxGIJ)
    if ($ExchangeMajorVersion -ge 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)”
    }

  10. 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!

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

  12. 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!

  13. 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.

  14. 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.

  15. 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.

  16. 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.

  17. 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.

  18. 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)
    }

Leave a Reply