Public/Get-O365MailboxUsage.ps1

function Get-O365MailboxUsage {
    <#
    .SYNOPSIS
        Retrieves Office 365 mailbox usage statistics showing storage utilization and percentage full.
     
    .DESCRIPTION
        This function connects to Exchange Online and retrieves mailbox statistics including current usage,
        total quota, and percentage full. Results are sorted by highest usage percentage by default.
        Can process specific users from pipeline input or return all mailboxes.
     
    .PARAMETER UserPrincipalName
        Specific user principal name (email address) to retrieve statistics for.
        Can accept pipeline input.
     
    .PARAMETER SortBy
        Property to sort results by. Valid values: PercentFull, UsedSpaceGB, TotalQuotaGB, DisplayName
        Default is PercentFull (descending).
     
    .PARAMETER IncludeArchive
        Include archive mailbox statistics if available.
     
    .PARAMETER ExportToCsv
        Export results to a CSV file in C:\Temp folder.
     
    .PARAMETER CsvPath
        Custom path for CSV export. If not specified, defaults to C:\Temp\O365MailboxUsage_YYYYMMDD_HHMMSS.csv
     
    .EXAMPLE
        Get-O365MailboxUsage
        Returns all mailboxes sorted by percentage full (highest first).
     
    .EXAMPLE
        "user@domain.com" | Get-O365MailboxUsage
        Returns statistics for specific user from pipeline input.
 
    .EXAMPLE
        # Process multiple users
        @("user1@domain.com", "user2@domain.com") | Get-O365MailboxUsage
     
    .EXAMPLE
        Get-O365MailboxUsage -SortBy UsedSpaceGB -IncludeArchive
        Returns all mailboxes sorted by used space including archive statistics.
     
    .EXAMPLE
        Get-O365MailboxUsage -ExportToCsv
        Returns all mailboxes and exports results to CSV in C:\Temp folder.
     
    .EXAMPLE
        Get-O365MailboxUsage -CsvPath "D:\Reports\mailbox_report.csv"
        Returns all mailboxes and exports to a custom CSV path.
     
    .NOTES
        Requires Exchange Online PowerShell module and appropriate permissions.
        Run Connect-ExchangeOnline before using this function.
    #>

    
    [CmdletBinding()]
    param(
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias("Identity", "PrimarySmtpAddress", "Email")]
        [string[]]$UserPrincipalName,
        
        [Parameter()]
        [ValidateSet("PercentFull", "UsedSpaceGB", "TotalQuotaGB", "DisplayName")]
        [string]$SortBy = "PercentFull",
        
        [Parameter()]
        [switch]$IncludeArchive,
        
        [Parameter(
            HelpMessage = "Export results to CSV file in C:\Temp folder"
        )]
        [switch]$ExportToCsv,
        
        [Parameter(
            HelpMessage = "Custom path for CSV export"
        )]
        [ValidateNotNullOrEmpty()]
        [string]$CsvPath
    )
    
    begin {
        # Check if Exchange Online connection exists and is functional
        if (-not (Test-O365ExchangeConnection -Quiet)) {
            Write-Host "Exchange Online connection required but not found." -ForegroundColor Yellow
            Write-Host "Attempting to establish connection..." -ForegroundColor Cyan
            
            if (-not (Connect-O365Exchange -ShowProgress)) {
                Write-Error "Failed to connect to Exchange Online. Please run Connect-O365Exchange manually."
                return
            }
        }
        
        Write-Verbose "Exchange Online connection verified and ready"
        
        # Initialize collection for results
        $results = @()
        
        # Function to convert bytes to GB with proper formatting
        function ConvertTo-GB {
            param([string]$SizeString)
            
            if ([string]::IsNullOrEmpty($SizeString) -or $SizeString -eq "Unlimited") {
                return 0
            }
            
            # Extract numeric value and convert to GB
            if ($SizeString -match '([\d,]+\.?\d*)\s*([KMGT]?B)') {
                $value = [double]($matches[1] -replace ',', '')
                $unit = $matches[2]
                
                switch ($unit) {
                    "TB" { return [math]::Round($value * 1024, 2) }
                    "GB" { return [math]::Round($value, 2) }
                    "MB" { return [math]::Round($value / 1024, 2) }
                    "KB" { return [math]::Round($value / 1048576, 2) }
                    "B"  { return [math]::Round($value / 1073741824, 2) }
                    default { return [math]::Round($value, 2) }
                }
            }
            return 0
        }
        
        # Function to process mailbox statistics
        function ProcessMailboxStats {
            param(
                [object]$Mailbox,
                [object]$Stats,
                [object]$ArchiveStats = $null
            )
            
            # Convert sizes to GB
            $usedSpaceGB = ConvertTo-GB -SizeString $Stats.TotalItemSize
            $totalQuotaGB = ConvertTo-GB -SizeString $Stats.ProhibitSendReceiveQuota
            
            # Calculate percentage full (avoid division by zero)
            $percentFull = if ($totalQuotaGB -gt 0) { 
                [math]::Round(($usedSpaceGB / $totalQuotaGB) * 100, 1) 
            } else { 
                0 
            }
            
            # Create result object
            $result = [PSCustomObject]@{
                DisplayName = $Mailbox.DisplayName
                UserPrincipalName = $Mailbox.PrimarySmtpAddress
                UsedSpaceGB = $usedSpaceGB
                TotalQuotaGB = $totalQuotaGB
                PercentFull = $percentFull
                ItemCount = $Stats.ItemCount
                LastLogonTime = $Stats.LastLogonTime
                MailboxType = $Mailbox.RecipientTypeDetails
            }
            
            # Add archive information if requested and available
            if ($IncludeArchive -and $ArchiveStats) {
                $archiveUsedGB = ConvertTo-GB -SizeString $ArchiveStats.TotalItemSize
                $archiveTotalGB = ConvertTo-GB -SizeString $ArchiveStats.ProhibitSendReceiveQuota
                $archivePercentFull = if ($archiveTotalGB -gt 0) { 
                    [math]::Round(($archiveUsedGB / $archiveTotalGB) * 100, 1) 
                } else { 
                    0 
                }
                
                $result | Add-Member -NotePropertyName "ArchiveUsedSpaceGB" -NotePropertyValue $archiveUsedGB
                $result | Add-Member -NotePropertyName "ArchiveTotalQuotaGB" -NotePropertyValue $archiveTotalGB
                $result | Add-Member -NotePropertyName "ArchivePercentFull" -NotePropertyValue $archivePercentFull
                $result | Add-Member -NotePropertyName "ArchiveItemCount" -NotePropertyValue $ArchiveStats.ItemCount
            }
            
            return $result
        }
    }
    
    process {
        try {
            # Determine which mailboxes to process
            if ($UserPrincipalName) {
                # Process specific users from pipeline or parameter
                foreach ($upn in $UserPrincipalName) {
                    Write-Verbose "Processing mailbox: $upn"
                    
                    # Get mailbox information
                    $mailbox = Get-Mailbox -Identity $upn -ErrorAction Stop
                    
                    # Get mailbox statistics
                    $stats = Get-MailboxStatistics -Identity $upn -ErrorAction Stop
                    
                    # Get archive statistics if requested
                    $archiveStats = $null
                    if ($IncludeArchive -and $mailbox.ArchiveStatus -eq "Active") {
                        try {
                            $archiveStats = Get-MailboxStatistics -Identity $upn -Archive -ErrorAction Stop
                        }
                        catch {
                            Write-Warning "Could not retrieve archive statistics for ${upn}: $($_.Exception.Message)"
                        }
                    }
                    
                    # Process and add to results
                    $result = ProcessMailboxStats -Mailbox $mailbox -Stats $stats -ArchiveStats $archiveStats
                    $results += $result
                }
            }
            else {
                # Get all mailboxes
                Write-Verbose "Retrieving all mailboxes..."
                $mailboxes = Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox, SharedMailbox, RoomMailbox, EquipmentMailbox
                
                $totalCount = $mailboxes.Count
                $currentCount = 0
                
                foreach ($mailbox in $mailboxes) {
                    $currentCount++
                    $percentComplete = [math]::Round(($currentCount / $totalCount) * 100, 0)
                    
                    Write-Progress -Activity "Processing mailboxes" -Status "Processing $($mailbox.DisplayName) ($currentCount of $totalCount)" -PercentComplete $percentComplete
                    Write-Verbose "Processing mailbox: $($mailbox.PrimarySmtpAddress)"
                    
                    try {
                        # Get mailbox statistics
                        $stats = Get-MailboxStatistics -Identity $mailbox.PrimarySmtpAddress -ErrorAction Stop
                        
                        # Get archive statistics if requested
                        $archiveStats = $null
                        if ($IncludeArchive -and $mailbox.ArchiveStatus -eq "Active") {
                            try {
                                $archiveStats = Get-MailboxStatistics -Identity $mailbox.PrimarySmtpAddress -Archive -ErrorAction Stop
                            }
                            catch {
                                Write-Warning "Could not retrieve archive statistics for $($mailbox.PrimarySmtpAddress): $($_.Exception.Message)"
                            }
                        }
                        
                        # Process and add to results
                        $result = ProcessMailboxStats -Mailbox $mailbox -Stats $stats -ArchiveStats $archiveStats
                        $results += $result
                    }
                    catch {
                        Write-Warning "Could not retrieve statistics for $($mailbox.PrimarySmtpAddress): $($_.Exception.Message)"
                    }
                }
                
                Write-Progress -Activity "Processing mailboxes" -Completed
            }
        }
        catch {
            Write-Error "Error processing mailbox statistics: $($_.Exception.Message)"
            return
        }
    }
    
    end {
        if ($results.Count -eq 0) {
            Write-Warning "No mailbox statistics retrieved."
            return
        }
        
        Write-Verbose "Retrieved statistics for $($results.Count) mailboxes"
        
        # Sort results based on specified property
        switch ($SortBy) {
            "PercentFull" { 
                $sortedResults = $results | Sort-Object PercentFull -Descending 
            }
            "UsedSpaceGB" { 
                $sortedResults = $results | Sort-Object UsedSpaceGB -Descending 
            }
            "TotalQuotaGB" { 
                $sortedResults = $results | Sort-Object TotalQuotaGB -Descending 
            }
            "DisplayName" { 
                $sortedResults = $results | Sort-Object DisplayName 
            }
            default { 
                $sortedResults = $results | Sort-Object PercentFull -Descending 
            }
        }
        
        # Output results to pipeline
        Write-Output $sortedResults
        
        # Export to CSV if requested
        if ($ExportToCsv -or $CsvPath) {
            try {
                # Determine CSV path
                if ($CsvPath) {
                    $csvFilePath = $CsvPath
                    # Ensure directory exists
                    $csvDirectory = Split-Path $csvFilePath -Parent
                    if (-not (Test-Path $csvDirectory)) {
                        New-Item -Path $csvDirectory -ItemType Directory -Force | Out-Null
                    }
                } else {
                    # Create C:\Temp if it doesn't exist
                    if (-not (Test-Path "C:\Temp")) {
                        New-Item -Path "C:\Temp" -ItemType Directory -Force | Out-Null
                    }
                    
                    # Generate timestamped filename
                    $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
                    $csvFilePath = "C:\Temp\O365MailboxUsage_$timestamp.csv"
                }
                
                # Export to CSV
                $sortedResults | Export-Csv -Path $csvFilePath -NoTypeInformation -Encoding UTF8
                
                Write-Host "Results exported to: $csvFilePath" -ForegroundColor Green
                Write-Host "Total records exported: $($sortedResults.Count)" -ForegroundColor Cyan
                
                # Show file size
                $fileInfo = Get-Item $csvFilePath
                $fileSizeMB = [math]::Round($fileInfo.Length / 1MB, 2)
                Write-Host "File size: $fileSizeMB MB" -ForegroundColor Cyan
            }
            catch {
                Write-Warning "Failed to export to CSV: $($_.Exception.Message)"
            }
        }
    }
}