Private/Write-AACSkuAvailabilityPdf.ps1
|
function Write-AACSkuAvailabilityPdf { <# .SYNOPSIS Writes Get-AACSkuAvailability's result as a landscape A4 PDF. .DESCRIPTION 1. Summary: title, scope, tiles, notices, the zones. 2. vCPU quota and the AKS cluster's node pools. 3. VM sizes, usable first, up to -MaxRows; the CSV and HTML report have them all. -Path must be a full path; see Save-AACPdfDocument. #> [CmdletBinding()] [OutputType([System.IO.FileInfo])] param( [Parameter(Mandatory)] [hashtable] $Availability, [Parameter(Mandatory)] [string] $Path, [Parameter(Mandatory)] [string] $Title, [System.Collections.IDictionary] $Detail, [int] $MaxRows = 3000 ) $stats = $Availability.Stats $pdf = New-AACPdfDocument -Title $Title -Subject "VM size availability: $($stats.Sizes) sizes" -Landscape $section = $pdf.Section $colors = $pdf.Colors $pt = $pdf.Pt $right = [MigraDoc.DocumentObjectModel.ParagraphAlignment]::Right $statusColor = @{ Available = $pdf.Tone.Good.Solid; Partial = $pdf.Tone.Warn.Solid; NoQuota = $pdf.Tone.Warn.Solid; ZoneUnavailable = $pdf.Tone.Bad.Solid; NotSupported = $pdf.Tone.Bad.Solid; Restricted = $pdf.Tone.Bad.Solid; NotChecked = $colors.Muted } $number = { param($Cell, $Value) $p = $Cell.AddParagraph($(if ($null -eq $Value) { '-' } else { '{0:N0}' -f [double]$Value })); $p.Format.Alignment = $right; if ($null -eq $Value) { $p.Format.Font.Color = $colors.Muted } } $status = { param($Cell, [string] $Name) $p = $Cell.AddParagraph($Name); $p.Format.Font.Name = 'Segoe UI Semibold'; $p.Format.Font.Color = $statusColor[$Name] } # --- 1. Summary ----------------------------------------------------------------------------- & $pdf.AddTitle "VM size availability · generated $($pdf.Generated.ToString('dddd d MMMM yyyy, HH:mm'))" $facts = [ordered]@{} if ($script:AACSession) { $facts['Azure account'] = [string]$script:AACSession.Account; $facts['Tenant'] = [string]$script:AACSession.TenantId } if ($Detail) { foreach ($key in $Detail.Keys) { $facts[[string]$key] = [string]$Detail[$key] } } foreach ($group in @($Availability.Zones | Group-Object -Property SubscriptionName, Location)) { $facts["Zones: $($group.Group[0].Location), $($group.Group[0].SubscriptionName)"] = (@($group.Group | Sort-Object LogicalZone | ForEach-Object { "$($_.LogicalZone) = $($_.PhysicalZone)" }) -join ', ') } $factTable = & $pdf.NewTable @(6.0, ($pdf.PageWidth - 6.0)) foreach ($key in $facts.Keys) { $row = & $pdf.AddBodyRow $factTable $row.Cells[0].AddParagraph($key).Format.Font.Color = $colors.Muted $row.Cells[1].AddParagraph($facts[$key]) | Out-Null } $section.AddParagraph().Format.SpaceAfter = & $pt 6 $tileData = @( @{ Value = '{0:N0}' -f $stats.Sizes; Label = 'sizes checked' } @{ Value = '{0:N0}' -f $stats.Available; Label = 'available'; Color = $(if ($stats.Available) { $pdf.Tone.Good.Solid }) } @{ Value = '{0:N0}' -f $stats.Partial; Label = 'in some zones only'; Color = $(if ($stats.Partial) { $pdf.Tone.Warn.Solid }) } @{ Value = '{0:N0}' -f $stats.NoQuota; Label = 'short of quota'; Color = $(if ($stats.NoQuota) { $pdf.Tone.Warn.Solid }) } @{ Value = '{0:N0}' -f ($stats.ZoneUnavailable + $stats.NotSupported); Label = 'not in the zones / not for AKS'; Color = $(if ($stats.ZoneUnavailable + $stats.NotSupported) { $pdf.Tone.Bad.Solid }) } @{ Value = '{0:N0}' -f $stats.Restricted; Label = 'restricted'; Color = $(if ($stats.Restricted) { $pdf.Tone.Bad.Solid }) } ) $tiles = & $pdf.NewTable @(1..$tileData.Count | ForEach-Object { $pdf.PageWidth / $tileData.Count }) $tiles.TopPadding = & $pt 8 $tiles.BottomPadding = & $pt 8 $tileRow = $tiles.AddRow() for ($i = 0; $i -lt $tileData.Count; $i++) { $cell = $tileRow.Cells[$i] $cell.Shading.Color = $colors.Panel $cell.Borders.Left.Width = $(if ($i -gt 0) { 2 } else { 0 }) $cell.Borders.Left.Color = $colors.White $value = $cell.AddParagraph([string]$tileData[$i].Value) if ($tileData[$i].Contains('Color') -and $tileData[$i].Color) { $value.Format.Font.Color = $tileData[$i].Color } $value.Format.Alignment = [MigraDoc.DocumentObjectModel.ParagraphAlignment]::Center $value.Format.Font.Size = 18 $value.Format.Font.Name = 'Segoe UI Semibold' $caption = $cell.AddParagraph($tileData[$i].Label) $caption.Format.Alignment = [MigraDoc.DocumentObjectModel.ParagraphAlignment]::Center $caption.Format.Font.Size = 8 $caption.Format.Font.Color = $colors.Muted } foreach ($text in @(@($Availability.Notice | Where-Object { $_ }) + 'Read-only: Microsoft.Compute SKUs (offered zones and the subscription''s restrictions), vCPU usage and the zone mapping. Capacity at deployment time can''t be checked without deploying.')) { $p = $section.AddParagraph($text); $p.Format.Font.Size = 8; $p.Format.Font.Color = $colors.Muted; $p.Format.SpaceBefore = & $pt 4 } # --- 2. Quota and node pools ---------------------------------------------------------------------- if (@($Availability.Quotas).Count) { $section.AddParagraph('vCPU quota', 'Heading2') | Out-Null $table = & $pdf.NewTable @(9.0, 3.0, 3.0, 3.0, 2.4, 3.6, 3.6) & $pdf.AddHeaderRow $table @('Quota', 'Used', 'Limit', 'Free', 'Used %', 'Region', 'Subscription') @(1, 2, 3, 4) foreach ($item in $Availability.Quotas) { $row = & $pdf.AddBodyRow $table $name = $row.Cells[0].AddParagraph($item.Quota); if (-not $item.Family) { $name.Format.Font.Name = 'Segoe UI Semibold' } & $number $row.Cells[1] $item.Used & $number $row.Cells[2] $item.Limit & $number $row.Cells[3] $item.Free $p = $row.Cells[4].AddParagraph($(if ($null -ne $item.UsedPercent) { "$($item.UsedPercent)%" } else { '-' })); $p.Format.Alignment = $right if ($null -ne $item.UsedPercent -and $item.UsedPercent -ge 90) { $p.Format.Font.Color = $pdf.Tone.Bad.Solid } elseif ($null -ne $item.UsedPercent -and $item.UsedPercent -ge 70) { $p.Format.Font.Color = $pdf.Tone.Warn.Solid } $row.Cells[5].AddParagraph($item.Location) | Out-Null $row.Cells[6].AddParagraph($item.SubscriptionName) | Out-Null } } if (@($Availability.Pools).Count) { $section.AddParagraph('AKS node pools', 'Heading2') | Out-Null $table = & $pdf.NewTable @(3.6, 4.4, 1.8, 2.0, 3.0, 2.6, 10.2) & $pdf.AddHeaderRow $table @('Pool', 'Size', 'Nodes', 'Zones', 'Size status', 'Family free', 'Why') @(2, 5) foreach ($item in $Availability.Pools) { $row = & $pdf.AddBodyRow $table $row.Cells[0].AddParagraph($item.Pool).Format.Font.Name = 'Segoe UI Semibold' if ($item.Mode) { $n = $row.Cells[0].AddParagraph($item.Mode); $n.Format.Font.Size = 7; $n.Format.Font.Color = $colors.Muted } $row.Cells[1].AddParagraph($item.VmSize) | Out-Null & $number $row.Cells[2] $item.Nodes $row.Cells[3].AddParagraph($(if ($item.Zones) { $item.Zones } else { 'none' })) | Out-Null & $status $row.Cells[4] $item.SizeStatus & $number $row.Cells[5] $item.FamilyVCpuFree $row.Cells[6].AddParagraph($item.Reason).Format.Font.Size = 7 } } # --- 3. Sizes ----------------------------------------------------------------------------------- $skus = @($Availability.Skus) if ($skus.Count) { $section.AddPageBreak() $section.AddParagraph('VM sizes', 'Heading2') | Out-Null $table = & $pdf.NewTable @(5.0, 3.0, 1.4, 1.8, 1.8, 2.2, 4.2, 8.2) & $pdf.AddHeaderRow $table @('Size', 'Status', 'vCPUs', 'Memory GB', 'Zones', 'Family free', 'Features', 'Why') @(2, 3, 5) foreach ($item in @($skus | Select-Object -First $MaxRows)) { $row = & $pdf.AddBodyRow $table $row.Cells[0].AddParagraph($item.Sku).Format.Font.Name = 'Segoe UI Semibold' if ($stats.Locations -gt 1 -or $stats.Subscriptions -gt 1) { $n = $row.Cells[0].AddParagraph("$($item.Location) · $($item.SubscriptionName)"); $n.Format.Font.Size = 7; $n.Format.Font.Color = $colors.Muted } & $status $row.Cells[1] $item.Status & $number $row.Cells[2] $item.vCPUs $p = $row.Cells[3].AddParagraph(('{0:0.##}' -f $item.MemoryGB)); $p.Format.Alignment = $right $row.Cells[4].AddParagraph($(if ($item.Zones) { $item.Zones } else { 'none' })) | Out-Null & $number $row.Cells[5] $item.FamilyVCpuFree $features = @(if ($item.Architecture -eq 'Arm64') { 'Arm64' }; if ($item.GPUs) { "$($item.GPUs) GPU" }; if ($item.EphemeralOSDisk) { 'ephemeral OS' }; if ($item.AcceleratedNetworking) { 'accel. net' }; if ($item.PremiumStorage) { 'premium' }) $row.Cells[6].AddParagraph($features -join ', ').Format.Font.Size = 7 $row.Cells[7].AddParagraph((@($item.Reason, $item.Note, $(if ($item.InUse) { "in use: $($item.InUse)" })) | Where-Object { $_ }) -join '; ').Format.Font.Size = 7 } if ($skus.Count -gt $MaxRows) { $p = $section.AddParagraph("The first $MaxRows sizes are listed; the CSV and HTML report have them all."); $p.Format.Font.Color = $colors.Muted } } Save-AACPdfDocument -Pdf $pdf -Path $Path } |