Public/Export-M365RMailboxSizes.ps1

#Requires -Version 5 -Modules ExchangeOnlineManagement, ImportExcel
function Export-M365RMailboxSizes
{
    <#
        .SYNOPSIS
            Name: Export-EXOMailboxSizes.ps1
            This gathers mailbox size information including primary and archive size and item count and exports to a XLSX file.
            NOTE: If you are not connected to Exchange Online Management prior to the running of this command then the created connection will not be preserved
 
        .DESCRIPTION
            This script connects to EXO and then outputs Mailbox statistics to a CSV file.
 
        .NOTES
            Version: 0.12
            Updated: 03-03-2022 v0.12 Replaced Export-Csv with Export-Excel (from ImportExcel Module) and added Summary page
            Updated: 01-03-2022 v0.11 Updated to an export command that calls the get command
            Updated: 01-03-2022 v0.10 Included a paramter to use an input CSV file
            Updated: 06-01-2022 v0.9 Changed output file date to match order of ISO8601 standard
            Updated: 10-11-2021 v0.8 Added parameter sets to prevent use of mutually exclusive parameters
                                        Disabled write-progress if the verbose parameter is used
            Updated: 10-11-2021 v0.7 Updated to include inactive mailboxes and improved error handling
            Updated: 08-11-2021 v0.6 Fixed an issue where archive stats are not included in output if the first mailbox does not have an archive
                                        Updated filename ordering
            Updated: 19-10-2021 v0.5 Updated to use Generic List instead of ArrayList
            Updated: 18-10-2021 v0.4 Updated formatting
            Updated: 15-10-2021 v0.3 Refactored for new parameters, error handling and verbose messaging
            Updated: 14-10-2021 v0.2 Rewritten to improve speed, remove superflous information
            Updated: <unknown> v0.1 Initial draft
 
            Authors: Luke Allinson (github:LukeAllinson)
                    Robin Dadswell (github:RobinDadswell)
 
        .PARAMETER OutputPath
            Full path to the folder where the output will be saved.
            Can be used without the parameter name in the first position only.
 
        .PARAMETER InactiveMailboxOnly
            Only gathers information about inactive mailboxes (active mailboxes are not included in results).
 
        .PARAMETER IncludeInactiveMailboxes
            Include inactive mailboxes in results; these are not included by default.
 
        .PARAMETER RecipientTypeDetails
            Provide one or more RecipientTypeDetails values to return only mailboxes of those types in the results. Seperate multiple values by commas.
            Valid values are: EquipmentMailbox, GroupMailbox, RoomMailbox, SharedMailbox, TeamMailbox, UserMailbox.
 
        .PARAMETER MailboxFilter
            Provide a filter to reduce the size of the Get-EXOMailbox query; this must follow oPath syntax standards.
            For example:
            'EmailAddresses -like "*bruce*"'
            'DisplayName -like "*wayne*"'
            'CustomAttribute1 -eq "InScope"'
 
        .PARAMETER Filter
            Alias of MailboxFilter parameter.
 
        .PARAMETER InputCSV
            Full path and filename to an input CSV to specify which mailboxes will be included in the report.
            The CSV must contain a 'UserPrincipalName' or 'PrimarySmtpAddress' or 'EmailAddress' column/header.
            If multiple are found, 'UserPrincipalName' is preferred if found, otherwise 'PrimarySmtpAddress'; 'EmailAddress' is included to cater for exports from non-Exchange (e.g. HR) systems or manually created files.
            Note: All mailboxes are still retrieved and then compared to the CSV to ensure all requested information is captured.
            Note2: Progress is shown as overall progress of all mailboxes plus progress of CSV contents.
 
        .EXAMPLE
            .\Export-EXOMailboxSizes.ps1 C:\Scripts\
            Exports size information for all mailbox types
 
        .EXAMPLE
            .\Export-EXOMailboxSizes.ps1 -RecipientTypeDetails RoomMailbox,EquipmentMailbox -OutputPath C:\Scripts\
            Exports size information only for Room and Equipment mailboxes
 
        .EXAMPLE
            .\Export-EXOMailboxSizes.ps1 C:\Scripts\ -MailboxFilter 'Department -eq "R&D"'
            Exports size information for all mailboxes from the R&D department
    #>


    [CmdletBinding(DefaultParameterSetName = 'DefaultParameters')]
    [OutputType([string])]
    param
    (
        [Parameter(
            Mandatory,
            Position = 0,
            ParameterSetName = 'DefaultParameters'
        )]
        [Parameter(
            Mandatory,
            Position = 0,
            ParameterSetName = 'InactiveOnly'
        )]
        [Parameter(
            Mandatory,
            Position = 0,
            ParameterSetName = 'IncludeInactive'
        )]
        [Parameter(
            Mandatory,
            Position = 0,
            ParameterSetName = 'InputCSV'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateScript(
            {
                if (!(Test-Path -Path $_))
                {
                    throw "The folder $_ does not exist"
                }
                else
                {
                    return $true
                }
            }
        )]
        [IO.DirectoryInfo]
        $OutputPath,
        [Parameter(
            ParameterSetName = 'InactiveOnly'
        )]
        [switch]
        $InactiveMailboxOnly,
        [Parameter(
            ParameterSetName = 'IncludeInactive'
        )]
        [switch]
        $IncludeInactiveMailboxes,
        [Parameter(
            ParameterSetName = 'DefaultParameters'
        )]
        [Parameter(
            ParameterSetName = 'InactiveOnly'
        )]
        [Parameter(
            ParameterSetName = 'IncludeInactive'
        )]
        [ValidateSet(
            'EquipmentMailbox',
            'GroupMailbox',
            'RoomMailbox',
            'SharedMailbox',
            'TeamMailbox',
            'UserMailbox'
        )]
        [string[]]
        $RecipientTypeDetails,
        [Parameter(
            ParameterSetName = 'DefaultParameters'
        )]
        [Parameter(
            ParameterSetName = 'InactiveOnly'
        )]
        [Parameter(
            ParameterSetName = 'IncludeInactive'
        )]
        [Alias('Filter')]
        [string]
        $MailboxFilter,
        [Parameter(
            ParameterSetName = 'InputCSV'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateScript(
            {
                if (!(Test-Path -Path $_))
                {
                    throw "The file $_ does not exist"
                }
                else
                {
                    return $true
                }
            }
        )]
        [IO.FileInfo]
        $InputCSV
    )

    $commandHashTable = @{}

    if ($IncludeInactiveMailboxes)
    {
        $commandHashTable['IncludeInactiveMailbox'] = $true
    }

    if ($InactiveMailboxOnly)
    {
        $commandHashTable['InactiveMailboxOnly'] = $true
    }

    if ($RecipientTypeDetails)
    {
        $commandHashTable['RecipientTypeDetails'] = $RecipientTypeDetails
    }

    if ($MailboxFilter)
    {
        $commandHashTable['Filter'] = $MailboxFilter
    }

    if ($InputCSV)
    {
        $commandHashTable['InputCSV'] = $InputCSV
    }

    if ($DeviceAuthentication)
    {
        $commandHashTable['DeviceAuthentication'] = $true
    }

    $timeStamp = Get-Date -Format yyyyMMdd-HHmm
    $outputFile = $OutputPath.FullName.TrimEnd([System.IO.Path]::DirectorySeparatorChar) + [System.IO.Path]::DirectorySeparatorChar + 'EXOMailboxSizes' + '_' + $timeStamp + '.xlsx'

    $output = Get-M365RMailboxSizes @commandHashTable

    if ($output.Count -ge 1)
    {
        $output | Export-Excel -Path $outputFile -WorksheetName 'MailboxStats' -FreezeTopRow -AutoSize -AutoFilter -TableName 'MailboxStats'

        ### Add summary sheet and apply formatting
        $summaryPage = New-Object System.Collections.Generic.List[System.Object]
        $mailboxTypes = ('UserMailbox', 'SharedMailbox', 'RoomMailbox', 'EquipmentMailbox')
        $t = 3
        foreach ($mailboxType in $mailboxTypes)
        {
            $summaryStats = [ordered]@{
                'MailboxType'             = $mailboxType
                'MailboxCount'            = "=COUNTIF(MailboxStats[RecipientTypeDetails],A$t)"
                'TotalSize(MB)'           = "=SUMIF(MailboxStats[RecipientTypeDetails],A$t,MailboxStats[TotalItemSize(MB)])"
                'AverageSize(MB)'         = "=IF(C$t<>0,(AVERAGEIF(MailboxStats[RecipientTypeDetails],A$t,MailboxStats[TotalItemSize(MB)])),0)"
                'TotalItemCount'          = "=SUMIF(MailboxStats[RecipientTypeDetails],A$t,MailboxStats[ItemCount])"
                'AverageItemCount'        = "=IF(E$t<>0,(AVERAGEIF(MailboxStats[RecipientTypeDetails],A$t,MailboxStats[TotalItemSize(MB)])),0)"
                'ArchiveCount'            = "=COUNTIFS(MailboxStats[RecipientTypeDetails],A$t,MailboxStats[ArchiveStatus],""Active"")"
                'ArchiveTotalSize(MB)'    = "=SUMIF(MailboxStats[RecipientTypeDetails],A$t,MailboxStats[Archive_TotalItemSize(MB)])"
                'ArchiveAverageSize(MB)'  = "=IF(H$t<>0,(AVERAGEIF(MailboxStats[RecipientTypeDetails],A$t,MailboxStats[Archive_TotalItemSize(MB)])),0)"
                'ArchiveTotalItemCount'   = "=SUMIF(MailboxStats[RecipientTypeDetails],A$t,MailboxStats[Archive_ItemCount])"
                'ArchiveAverageItemCount' = "=IF(J$t<>0,(AVERAGEIF(MailboxStats[RecipientTypeDetails],A$t,MailboxStats[Archive_ItemCount])),0)"
            }
            $summaryPage.Add([PSCustomObject]$summaryStats) | Out-Null
            $t++
        }
        $summaryStats = [ordered]@{
            'MailboxType'             = 'Total'
            'MailboxCount'            = '=SUM(B3:B6)'
            'TotalSize(MB)'           = '=SUM(C3:C6)'
            'AverageSize(MB)'         = '=SUM(D3:D6)'
            'TotalItemCount'          = '=SUM(E3:E6)'
            'AverageItemCount'        = '=SUM(F3:F6)'
            'ArchiveCount'            = '=SUM(G3:G6)'
            'ArchiveTotalSize(MB)'    = '=SUM(H3:H6)'
            'ArchiveAverageSize(MB)'  = '=SUM(I3:I6)'
            'ArchiveTotalItemCount'   = '=SUM(J3:J6)'
            'ArchiveAverageItemCount' = '=SUM(K3:K6)'
        }
        $summaryPage.Add([PSCustomObject]$summaryStats) | Out-Null
        $summaryPage | Export-Excel -Path $outputFile -WorksheetName 'Summary' -TableName 'Summary' -AutoSize -StartRow 2 -MoveToStart
        $output | Sort-Object 'TotalItemSize(MB)' -Descending | Select-Object UserPrincipalName, DisplayName, RecipientTypeDetails, 'TotalItemSize(MB)', ItemCount -First 10 | Export-Excel -Path $outputFile -WorksheetName 'Summary' -TableName 'Top10BySize' -StartRow 10
        $output | Sort-Object ItemCount -Descending | Select-Object UserPrincipalName, DisplayName, RecipientTypeDetails, 'TotalItemSize(MB)', ItemCount -First 10 | Export-Excel -Path $outputFile -WorksheetName 'Summary' -TableName 'Top10ByItemCount' -StartRow 23
        $output | Sort-Object 'Archive_TotalItemSize(MB)' -Descending | Select-Object UserPrincipalName, DisplayName, RecipientTypeDetails, 'Archive_TotalItemSize(MB)', Archive_ItemCount -First 10 | Export-Excel -Path $outputFile -WorksheetName 'Summary' -TableName 'Top10ByArchiveSize' -StartRow 36
        $output | Sort-Object Archive_ItemCount -Descending | Select-Object UserPrincipalName, DisplayName, RecipientTypeDetails, 'Archive_TotalItemSize(MB)', Archive_ItemCount -First 10 | Export-Excel -Path $outputFile -WorksheetName 'Summary' -TableName 'Top10ByArchiveItemCount' -StartRow 49
        $excelPkg = Open-ExcelPackage -Path $outputFile
        $summarySheet = $excelPkg.Workbook.Worksheets['Summary']
        $summarySheet.Cells[1, 1].Value = 'Summary'
        $summarySheet.Cells[9, 1].Value = 'Top10 Mailboxes By Size'
        $summarySheet.Cells[22, 1].Value = 'Top10 Mailboxes By ItemCount'
        $summarySheet.Cells[35, 1].Value = 'Top10 Archives By Size'
        $summarySheet.Cells[48, 1].Value = 'Top10 Archives By ItemCount'
        $summarySheet.Select('A1')
        $summarySheet.SelectedRange.Style.Font.Size = 16
        $summarySheet.SelectedRange.Style.Font.Bold = $true
        $summarySheetTitleCells = ('A9', 'A22', 'A35', 'A48')
        ForEach ($cell in $summarySheetTitleCells)
        {
            $summarySheet.Select($cell)
            $summarySheet.SelectedRange.Style.Font.Size = 13
            $summarySheet.SelectedRange.Style.Font.Bold = $true
        }
        $summarySheetBoldRanges = ('A7:K7', 'D11:D20', 'E24:E33', 'D37:D46', 'E50:E59')
        ForEach ($range in $summarySheetBoldRanges)
        {
            $summarySheet.Select($range)
            $summarySheet.SelectedRange.Style.Font.Bold = $true
        }
        $summarySheetAverageRanges = ('D3:D7', 'F3:F7', 'I3:I7', 'K3:K7')
        ForEach ($range in $summarySheetAverageRanges)
        {
            $summarySheet.Select($range)
            $summarySheet.SelectedRange.Style.Numberformat.Format = '0.00'
        }
        $fullRange = ($summarySheet.Dimension | Select-Object Address).Address
        $summarySheet.Select($fullRange)
        $summarySheet.SelectedRange.AutoFitColumns()
        $summarySheet.Select('A1')
        Close-ExcelPackage $excelPkg
        return "Mailbox size data has been exported to $outputfile"
    }
    else
    {
        return 'No results returned; no data exported'
    }
}