$server = Get-Content -Path ".\DefaultServer.txt" $credential = Import-Clixml -Path ".\AdminCredential.xml" # Create remote PowerShell session $session = $null # Initialize session variable try { Write-Host "Attempting to create remote PowerShell session to $server..." $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://$server/PowerShell/" -Credential $credential -Authentication Kerberos -ErrorAction Stop Import-PSSession $session -DisableNameChecking -ErrorAction Stop Write-Host "Session created and imported successfully." } catch { Write-Error "Failed to create or import remote PowerShell session: $($_.Exception.Message)" return } # --- Helper Functions (Unchanged) --- # Function to display formatted size values without bytes function Get-FormattedSize { param ([string]$sizeString) if ([string]::IsNullOrWhiteSpace($sizeString)) { return "N/A" } # Match pattern like "1.23 GB (1320702443 bytes)" if ($sizeString -match "^(.*?) \(") { return $matches[1].Trim() } # Match pattern like "1.23 GB" if the bytes part is missing elseif ($sizeString -match "(\d+(\.\d+)? (GB|TB|MB|KB))$") { return $matches[1].Trim() } else { Write-Verbose "Could not parse size string: $sizeString"; return $sizeString } } # Helper function to convert size string (e.g., "1.5 GB") to GB (for calculation/charting) function ConvertTo-GB { param ( [Parameter(Mandatory=$true)] [string]$sizeStr ) $value = $null $unit = $null # Try parsing the 'Value (Bytes)' format first if ($sizeStr -match "^(.*?) \((\d+) bytes\)") { $readablePart = $matches[1].Trim() # Now parse the readable part if ($readablePart -match "(\d+(\.\d+)?)\s*(GB|TB|MB|KB)") { # Added \s* for flexibility $value = [double]$matches[1] $unit = $matches[3] # Fall through to switch } else { Write-Warning "Could not parse readable part '$readablePart' from '$sizeStr' for GB conversion." return $null } } elseif ($sizeStr -match "(\d+(\.\d+)?)\s*(GB|TB|MB|KB)") { # Added \s* for flexibility # Handle cases where the string might *only* be the size $value = [double]$matches[1] $unit = $matches[3] # Fall through to switch } else { Write-Warning "Could not parse size string '$sizeStr' for GB conversion." return $null # Indicate conversion failure } # Perform conversion based on unit try { switch ($unit) { "TB" { return $value * 1024 } "GB" { return $value } "MB" { return $value / 1024 } "KB" { return $value / (1024*1024) } Default { return $null } # Should not happen with regex, but good practice } } catch { Write-Warning "Error converting value '$value' ($unit) to GB from string '$sizeStr': $($_.Exception.Message)" return $null } } # Function to calculate whitespace percentage correctly function Calculate-WhitespacePercentage { param ( [Parameter(Mandatory=$true)] [string]$totalSizeString, # Expects format like "1.25 GB" [Parameter(Mandatory=$true)] [string]$whitespaceString # Expects format like "500 MB" ) $totalValueGB = ConvertTo-GB -sizeStr $totalSizeString $whitespaceValueGB = ConvertTo-GB -sizeStr $whitespaceString if ($null -ne $totalValueGB -and $null -ne $whitespaceValueGB -and $totalValueGB -gt 0) { # Ensure whitespace isn't negative or larger than total (can happen with calculation delays) $whitespaceValueGB = [System.Math]::Max(0, $whitespaceValueGB) $whitespaceValueGB = [System.Math]::Min($totalValueGB, $whitespaceValueGB) $percentage = ($whitespaceValueGB / $totalValueGB) * 100 return "{0:N2}" -f $percentage } elseif ($null -eq $totalValueGB) { return "N/A (Total Size Error)" } elseif ($null -eq $whitespaceValueGB) { return "N/A (Whitespace Error)" } elseif ($totalValueGB -le 0) { return "N/A (Zero Total Size)" } else { return "N/A (Calculation Error)" } } # --- End Helper Functions --- # Retrieve mailbox database details Write-Host "Retrieving mailbox database status..." $databases = Get-MailboxDatabase -Status -ErrorAction SilentlyContinue if (-not $databases) { Write-Warning "No mailbox databases found or error retrieving them." if ($session -ne $null) { Remove-PSSession $session } return } # Initialize arrays for chart data $chartDbNames = @() $chartDbSizesGB = @() $dbSizeDataForSorting = @() # Array of objects for sorting # Get Report Generation Date $reportGeneratedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" # --- Start building HTML report --- # Using the new CSS styles provided $html = @" Exchange Database Size Report

Exchange Database Size Report

Report Generated: $reportGeneratedDate | Connected Exchange Server: $($server)
Column Definitions:
"@ # End of initial HTML string part # Loop through each database, build table row AND collect chart data Write-Host "Processing $($databases.Count) databases..." foreach ($db in $databases) { $dbName = $db.Name $dbServer = $db.ServerName $dbMounted = $db.Mounted $dbCircular = $db.CircularLoggingEnabled # --- Data for Table --- $totalSizeStringRaw = $db.DatabaseSize.ToString() $whitespaceStringRaw = $db.AvailableNewMailboxSpace.ToString() $totalSizeFormatted = Get-FormattedSize -sizeString $totalSizeStringRaw $whitespaceFormatted = Get-FormattedSize -sizeString $whitespaceStringRaw $whitespacePercent = Calculate-WhitespacePercentage -totalSizeString $totalSizeFormatted -whitespaceString $whitespaceFormatted # --- Data for Charts --- $dbSizeGB = ConvertTo-GB -sizeStr $totalSizeStringRaw if ($null -ne $dbSizeGB) { $chartDbNames += $dbName $chartDbSizesGB += $dbSizeGB $dbSizeDataForSorting += [PSCustomObject]@{ Name = $dbName; SizeGB = $dbSizeGB } } else { Write-Warning "Could not get valid size in GB for database '$dbName' (Raw: '$totalSizeStringRaw'). Skipping for charts." } # Get Mailbox Count $mailboxCount = "N/A" try { $mbxQuery = Get-Mailbox -Database $db.Identity -ResultSize Unlimited -ErrorAction Stop $mailboxCount = $mbxQuery.Count } catch { Write-Warning "Error retrieving mailbox count for database '$dbName': $($_.Exception.Message)" $mailboxCount = "Error ($($_.Exception.GetType().Name))" } # Get Last Full Backup Date $lastBackup = "N/A" if ($db.LastFullBackup -ne $null -and $db.LastFullBackup -is [DateTime]) { if ($db.LastFullBackup -gt ([DateTime]::MinValue)) { $lastBackup = $db.LastFullBackup.ToString("yyyy-MM-dd HH:mm") } else { $lastBackup = "No Backup Recorded (MinDate)" } } elseif ($db.LastFullBackup -eq $null) { $lastBackup = "No Backup Recorded" } else { $lastBackup = "Invalid Date Data"; Write-Warning "Unexpected data type for LastFullBackup on DB '$dbName': $($db.LastFullBackup.GetType().FullName)" } # Add row to HTML table $html += @" "@ } # End foreach database # Complete the HTML table structure $html += @"
Name Server Mounted Total Size Whitespace Whitespace (%) Mailbox Count Last Full Backup Circular Logging
$($dbName) $($dbServer) $($dbMounted) $($totalSizeFormatted) $($whitespaceFormatted) $($whitespacePercent) % $($mailboxCount) $($lastBackup) $($dbCircular)
"@ # --- Chart Generation Logic --- # Check if we have data for charts if ($chartDbNames.Count -gt 0) { # (Color calculation logic remains the same) $largestDbName = $null $secondLargestDbName = $null if ($dbSizeDataForSorting.Count -gt 0) { $sortedDbs = $dbSizeDataForSorting | Sort-Object -Property SizeGB -Descending $largestDbName = $sortedDbs[0].Name if ($sortedDbs.Count -gt 1) { $secondLargestDbName = $sortedDbs[1].Name } } $chartColors = @() $defaultColors = @( 'rgba(75, 192, 192, 0.8)', 'rgba(255, 206, 86, 0.8)', 'rgba(153, 102, 255, 0.8)', 'rgba(255, 159, 64, 0.8)', 'rgba(199, 199, 199, 0.8)', 'rgba(83, 102, 255, 0.8)' ) $colorIndex = 0 foreach ($name in $chartDbNames) { if ($name -eq $largestDbName) { $chartColors += 'rgba(54, 162, 235, 0.8)' } # Blue elseif ($name -eq $secondLargestDbName) { $chartColors += 'rgba(255, 99, 132, 0.8)' } # Red else { $chartColors += $defaultColors[$colorIndex % $defaultColors.Count]; $colorIndex++ } } # (Prepare data for JavaScript injection remains the same) $jsLabels = $chartDbNames | ForEach-Object { "'$($_ -replace "'", "\'")'" } $jsLabelsString = $jsLabels -join "," $jsDataString = $chartDbSizesGB -join "," $jsColorsString = ($chartColors | ForEach-Object { "'$_'" } | Out-String).Trim() -replace "`r`n", "," # --- UPDATED HTML FOR CHARTS --- # Using .charts-wrapper and .chart-container structure with H3 titles $html += @"

Database Size (Pie Chart)

Database Size (Column Chart)

"@ } else { # Use the .nodata class for the fallback message $html += @"

No valid database size data available to generate charts.

"@ } # --- End Chart Generation Logic --- # Complete the HTML report structure $html += @" "@ # Save the HTML report (NO CHANGES IN THIS SECTION) $outputDirectory = Join-Path -Path $env:USERPROFILE -ChildPath "Downloads" if (-not (Test-Path -Path $outputDirectory -PathType Container)) { Write-Warning "Downloads directory not found at '$outputDirectory'." try { Write-Host "Attempting to create directory: $outputDirectory" New-Item -Path $outputDirectory -ItemType Directory -Force -ErrorAction Stop | Out-Null Write-Host "Directory created successfully." } catch { Write-Error "Could not create output directory '$outputDirectory': $($_.Exception.Message)" $outputDirectory = $PSScriptRoot Write-Warning "Attempting to save report to script directory: $outputDirectory" if (-not (Test-Path -Path $outputDirectory -PathType Container)) { $outputDirectory = $PWD.Path Write-Warning "Script directory not found. Falling back to current directory: $outputDirectory" if (-not (Test-Path -Path $outputDirectory -PathType Container)) { Write-Error "Fallback directory $outputDirectory also not found. Cannot save report." if ($session -ne $null) { Remove-PSSession $session } exit 1 } } } } $reportFileName = "ExchangeDatabaseSizeReport.html" $outputFile = Join-Path -Path $outputDirectory -ChildPath $reportFileName try { [System.IO.File]::WriteAllText($outputFile, $html, [System.Text.UTF8Encoding]::new($false)) # UTF8 without BOM Write-Host "Report successfully saved to $outputFile" Invoke-Item $outputFile } catch { Write-Error "Failed to save HTML report to '$outputFile': $($_.Exception.Message)" } # Clean up the remote session (NO CHANGES IN THIS SECTION) if ($session -ne $null -and $session.State -eq 'Opened') { Write-Host "Removing remote PowerShell session..." Remove-PSSession $session } elseif ($session -ne $null) { Write-Warning "Session state is $($session.State). Attempting removal anyway." Remove-PSSession $session -ErrorAction SilentlyContinue } Write-Host "Script finished."