# Set Error Action Preference for the script $ErrorActionPreference = 'Stop' # Stop on terminating errors try { # Import admin credentials Write-Host "Loading configuration..." -ForegroundColor Gray $serverAddress = Get-Content -Path ".\DefaultServer.txt" -ErrorAction SilentlyContinue if (-not $serverAddress) { throw "Could not read server address from .\DefaultServer.txt" } $credential = Import-Clixml -Path .\AdminCredential.xml -ErrorAction SilentlyContinue if (-not $credential) { throw "Could not import credentials from .\AdminCredential.xml" } # Establish session with Exchange Server Write-Host "Connecting to Exchange server $serverAddress..." -ForegroundColor Yellow $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://$serverAddress/PowerShell/" -Authentication Kerberos -Credential $credential # ErrorAction Stop is implied by preference Import-PSSession $session -DisableNameChecking -WarningAction SilentlyContinue # ErrorAction Stop is implied by preference Write-Host "Successfully connected." -ForegroundColor Green # Define output file path in the user's Downloads folder $outputFile = Join-Path -Path $env:USERPROFILE -ChildPath "Downloads\ExchangeDatabaseMailboxListReport_$(Get-Date -Format 'yyyyMMdd_HHmm').html" Write-Host "Report will be saved to: $outputFile" -ForegroundColor Gray # --- Data Collection Variables --- $totalUserMailboxes = 0 $totalSharedMailboxes = 0 $databaseStats = @() # Array to hold stats for the column chart # --- HTML Report Setup --- Write-Host "Setting up HTML structure and styles..." -ForegroundColor Gray $head = @" "@ # Start building the HTML body content $scriptStartTime = Get-Date $bodyContent = "

Exchange Database Mailbox Report

" # Use provided context $bodyContent += "

Generated on: $($scriptStartTime.ToString('yyyy-MM-dd HH:mm:ss')) (AEST - Bexley, NSW)

" $bodyContent += "

Note: System mailboxes such as 'Discovery Search Mailbox' have been excluded. Size and Last Logon data retrieval can take significant time.

" # Get database size and mailboxes Write-Host "Retrieving database information..." -ForegroundColor Yellow $databases = Get-MailboxDatabase -Status | Select-Object Name, DatabaseSize Write-Host "Found $($databases.Count) databases." -ForegroundColor Green # --- Process Each Database --- foreach ($db in $databases) { $dbStartTime = Get-Date Write-Host "Processing database: $($db.Name)" -ForegroundColor Yellow $dbName = $db.Name $dbSize = $db.DatabaseSize -replace '\s*\(.*\)', '' # Clean up size string # Add database details to the HTML body $bodyContent += "

Database: $dbName

" $bodyContent += "

Total Size: $dbSize

" # Retrieve base mailboxes first Write-Host " Retrieving base mailbox list for $dbName..." -ForegroundColor Gray $baseMailboxes = Get-Mailbox -Database $dbName -ResultSize Unlimited | Where-Object {$_.DisplayName -ne "Discovery Search Mailbox"} | Select-Object Identity, DisplayName, PrimarySmtpAddress, RecipientTypeDetails Write-Host " Found $($baseMailboxes.Count) mailboxes to query for statistics." -ForegroundColor Gray # --- Get Detailed Stats (Slow Part) --- $detailedMailboxData = @() # Array to hold combined data for this DB Write-Host " Retrieving statistics for $($baseMailboxes.Count) mailboxes (this may take considerable time)..." -ForegroundColor Cyan $progressCount = 0 foreach ($mb in $baseMailboxes) { $progressCount++ Write-Progress -Activity "Getting Mailbox Stats for $($db.Name)" -Status "Processing mailbox $progressCount of $($baseMailboxes.Count): $($mb.DisplayName)" -PercentComplete (($progressCount / $baseMailboxes.Count) * 100) $mbSize = "N/A" $mbLastLogon = $null try { $stats = Get-MailboxStatistics -Identity $mb.Identity -ErrorAction Stop | Select-Object TotalItemSize, LastLogonTime if ($stats.TotalItemSize -ne $null) { $mbSize = "{0:N2}" -f $stats.TotalItemSize.ToMB() } $mbLastLogon = $stats.LastLogonTime } catch { Write-Warning "Could not retrieve statistics for mailbox $($mb.DisplayName) ($($mb.Identity)). Error: $($_.Exception.Message)" $mbSize = "Error" $mbLastLogon = $null } $detailedMailboxData += [PSCustomObject]@{ DisplayName = $mb.DisplayName PrimarySmtpAddress = $mb.PrimarySmtpAddress RecipientTypeDetails = $mb.RecipientTypeDetails MailboxSizeMB = $mbSize LastLogonTime = $mbLastLogon } } # End foreach mailbox statistics Write-Progress -Activity "Getting Mailbox Stats for $($db.Name)" -Completed Write-Host " Finished retrieving statistics for $dbName." -ForegroundColor Green # Filter and Sort the detailed data Write-Host " Filtering and sorting mailbox data..." -ForegroundColor Gray $userMailboxes = $detailedMailboxData | Where-Object {$_.RecipientTypeDetails -eq "UserMailbox"} | Sort-Object DisplayName $sharedMailboxes = $detailedMailboxData | Where-Object {$_.RecipientTypeDetails -eq "SharedMailbox"} | Sort-Object DisplayName # Accumulate total counts for charts $currentUserCount = $userMailboxes.Count $currentSharedCount = $sharedMailboxes.Count $totalUserMailboxes += $currentUserCount $totalSharedMailboxes += $currentSharedCount $databaseStats += [PSCustomObject]@{ DatabaseName = $dbName UserCount = $currentUserCount SharedCount = $currentSharedCount } # --- Add User Mailboxes Table (Manual HTML Generation) --- # START BLOCK TO REPLACE ConvertTo-Html Write-Host " Generating User Mailbox table (Manual Method)..." -ForegroundColor Gray $bodyContent += "

User Mailboxes

" if ($userMailboxes) { # Select data (still useful to get consistent objects) $userTableData = $userMailboxes | Select-Object @{Name='DisplayName'; Expression={$_.DisplayName}}, ` @{Name='PrimarySmtpAddress'; Expression={$_.PrimarySmtpAddress}}, ` @{Name='Size (MB)'; Expression={ "$($_.MailboxSizeMB)" }}, ` # Get as string @{Name='Last Logon'; Expression={ if ($_.LastLogonTime) { $_.LastLogonTime.ToString('yyyy-MM-dd HH:mm') } else { 'N/A' } }} # Get as string # Manual HTML Table Build $tableHtml = "

Count: $currentUserCount

" # Add alignment styling directly to TH elements $tableHtml += "" foreach ($row in $userTableData) { # Basic HTML Encoding for data fields $displayNameEsc = $row.DisplayName -replace '&', '&' -replace '<', '<' -replace '>', '>' $smtpAddressEsc = $row.PrimarySmtpAddress -replace '&', '&' -replace '<', '<' -replace '>', '>' $sizeEsc = $row.'Size (MB)' -replace '&', '&' -replace '<', '<' -replace '>', '>' $logonEsc = $row.'Last Logon' -replace '&', '&' -replace '<', '<' -replace '>', '>' $tableHtml += "" $tableHtml += "" $tableHtml += "" # Add alignment styling directly to TD elements $tableHtml += "" $tableHtml += "" $tableHtml += "" } $tableHtml += "
DisplayNamePrimarySmtpAddressSize (MB)Last Logon
$displayNameEsc$smtpAddressEsc$sizeEsc$logonEsc
" $bodyContent += $tableHtml # Append the manually built HTML } else { $bodyContent += "

No user mailboxes found in this database.

" } # END BLOCK TO REPLACE ConvertTo-Html # --- Add Shared Mailboxes Table (Manual HTML Generation) --- # START BLOCK TO REPLACE ConvertTo-Html Write-Host " Generating Shared Mailbox table (Manual Method)..." -ForegroundColor Gray $bodyContent += "

Shared Mailboxes

" if ($sharedMailboxes) { # Select data $sharedTableData = $sharedMailboxes | Select-Object @{Name='DisplayName'; Expression={$_.DisplayName}}, ` @{Name='PrimarySmtpAddress'; Expression={$_.PrimarySmtpAddress}}, ` @{Name='Size (MB)'; Expression={ "$($_.MailboxSizeMB)" }}, ` # Get as string @{Name='Last Logon'; Expression={ if ($_.LastLogonTime) { $_.LastLogonTime.ToString('yyyy-MM-dd HH:mm') } else { 'N/A' } }} # Get as string # Manual HTML Table Build $tableHtml = "

Count: $currentSharedCount

" # Add alignment styling directly to TH elements $tableHtml += "" foreach ($row in $sharedTableData) { # Basic HTML Encoding for data fields $displayNameEsc = $row.DisplayName -replace '&', '&' -replace '<', '<' -replace '>', '>' $smtpAddressEsc = $row.PrimarySmtpAddress -replace '&', '&' -replace '<', '<' -replace '>', '>' $sizeEsc = $row.'Size (MB)' -replace '&', '&' -replace '<', '<' -replace '>', '>' $logonEsc = $row.'Last Logon' -replace '&', '&' -replace '<', '<' -replace '>', '>' $tableHtml += "" $tableHtml += "" $tableHtml += "" # Add alignment styling directly to TD elements $tableHtml += "" $tableHtml += "" $tableHtml += "" } $tableHtml += "
DisplayNamePrimarySmtpAddressSize (MB)Last Logon
$displayNameEsc$smtpAddressEsc$sizeEsc$logonEsc
" $bodyContent += $tableHtml # Append the manually built HTML } else { $bodyContent += "

No shared mailboxes found in this database.

" } # END BLOCK TO REPLACE ConvertTo-Html $dbEndTime = Get-Date Write-Host " Finished processing $dbName in $(($dbEndTime - $dbStartTime).ToString('g'))." -ForegroundColor Green } # End foreach database # --- Add Chart Placeholders and Script --- Write-Host "Generating summary charts..." -ForegroundColor Yellow $bodyContent += "
" $bodyContent += "

Mailbox Count Summary Charts

" $bodyContent += "
" # Start charts wrapper $bodyContent += "

Total Mailbox Distribution

" # Pie Chart $bodyContent += "

Mailbox Counts per Database

" # Column Chart $bodyContent += "
" # End charts wrapper # --- Prepare Data for JavaScript --- $dbLabels = $databaseStats.DatabaseName | ForEach-Object { "'$($_ -replace "'", "\'")'" } # Quote names & escape internal quotes for JS $jsDbLabels = "[$($dbLabels -join ', ')]" $jsUserCounts = "[$($databaseStats.UserCount -join ', ')]" $jsSharedCounts = "[$($databaseStats.SharedCount -join ', ')]" # --- Embed JavaScript for Chart Generation --- # Using a self-executing anonymous function to scope variables $chartScript = @" "@ $bodyContent += $chartScript # --- Finalize and Save HTML Report --- $scriptEndTime = Get-Date $bodyContent += "

Report generation took: $(($scriptEndTime - $scriptStartTime).ToString('g'))

" # Add duration Write-Host "Generating final HTML report file..." -ForegroundColor Yellow ConvertTo-Html -Head $head -Body $bodyContent -Title "Exchange Database Mailbox Report" | Out-File -FilePath $outputFile -Encoding UTF8 -Force Write-Host "Report saved to $outputFile" -ForegroundColor Green # --- Open the file --- Write-Host "Attempting to open the report..." -ForegroundColor Cyan Invoke-Item -Path $outputFile # Clean up session Write-Host "Removing PSSession..." -ForegroundColor Gray Remove-PSSession $session Write-Host "Script finished successfully in $(($scriptEndTime - $scriptStartTime).ToString('g'))." -ForegroundColor Green } catch { # Log the error details Write-Host "An error occurred:" -ForegroundColor Red Write-Host "Error Message: $($_.Exception.Message)" -ForegroundColor Red # Display Line number if available if ($_.InvocationInfo) { Write-Host "Line Number: $($_.InvocationInfo.ScriptLineNumber)" -ForegroundColor Red # Write-Host "Line Content: $($_.InvocationInfo.Line)" -ForegroundColor Red # Uncomment to see the line content } # Write-Host "Stack Trace: $($_.ScriptStackTrace)" -ForegroundColor DarkGray # Uncomment for full stack trace if needed Write-Host "Script failed." -ForegroundColor Red # Attempt to remove session even if error occurred mid-script if ($PSBoundParameters.ContainsKey('session') -and $session -ne $null -and (Get-PSSession | Where-Object {$_.ID -eq $session.ID})) { Write-Host "Attempting to remove PSSession after error..." -ForegroundColor Yellow Remove-PSSession $session -ErrorAction SilentlyContinue } # Pause script window if running in console host before exit if ($Host.Name -eq 'ConsoleHost') { Read-Host -Prompt "Press Enter to exit" } # Exit with a non-zero code to indicate failure exit 1 } finally { # Any final cleanup that should always run (even after errors, if possible) Write-Progress -Activity "Cleanup" -Completed -ErrorAction SilentlyContinue } ```