Private/Write-AACAssessmentPdf.ps1
|
function Write-AACAssessmentPdf { <# .SYNOPSIS Writes the Invoke-AACAssessment report as a landscape A4 PDF. .DESCRIPTION 1. Summary: the scope, tiles, the subscriptions, the resources by category and the most common resource types. 2. A section per category (Overview, Compute, Networking, ..., Advisor, Security, Policy, Health, Cost) - each sheet with its key columns (a PDF page can't hold them all; the HTML and CSV have every column) and up to -RowLimit rows (most severe or most relevant first, as the sheet is sorted). Every category and sheet is a bookmark (Set-AACPdfOutline). -Path must be a full path; see Save-AACPdfDocument. #> [CmdletBinding()] [OutputType([System.IO.FileInfo])] param( [Parameter(Mandatory)] [hashtable] $Assessment, [Parameter(Mandatory)] [string] $Path, [Parameter(Mandatory)] [string] $Title, [System.Collections.IDictionary] $Detail, [ValidateRange(10, 5000)] [int] $RowLimit = 300 ) $stats = $Assessment.Stats $pdf = New-AACPdfDocument -Title $Title -Subject "$($stats.Resources) resources in $($stats.Subscriptions) subscriptions" -Landscape $section = $pdf.Section $colors = $pdf.Colors $pt = $pdf.Pt $small = { param($Paragraph) $Paragraph.Format.Font.Size = 7; $Paragraph } $right = [MigraDoc.DocumentObjectModel.ParagraphAlignment]::Right $severity = @{ High = $pdf.Tone.Bad.Solid; Critical = $pdf.Tone.Bad.Solid; Medium = $pdf.Tone.Warn.Solid; Yes = $pdf.Tone.Warn.Solid } $cellText = { param($Value) if ($null -eq $Value) { return '' } if ($Value -is [double] -or $Value -is [decimal]) { return ('{0:N2}' -f $Value) } if ($Value -is [int] -or $Value -is [long]) { return ('{0:N0}' -f $Value) } $text = [string]$Value if ($text.Length -gt 240) { $text = $text.Substring(0, 237) + '...' } $text } $writeTable = { param($Sheet, [string[]] $Columns) $shown = @($Columns | Where-Object { $Sheet.Columns -contains $_ } | Select-Object -First 8) if (-not $shown.Count) { $shown = @($Sheet.Columns | Select-Object -First 7) } $numeric = @(for ($i = 0; $i -lt $shown.Count; $i++) { if ([string]$Sheet.Types[$shown[$i]] -in 'number', 'money', 'score') { $i } }) $widths = @($shown | ForEach-Object { $pdf.PageWidth / $shown.Count }) $table = & $pdf.NewTable $widths & $pdf.AddHeaderRow $table $shown $numeric $rows = @($Sheet.Rows) foreach ($item in $rows | Select-Object -First $RowLimit) { $row = & $pdf.AddBodyRow $table for ($i = 0; $i -lt $shown.Count; $i++) { $value = $item.($shown[$i]) $p = & $small ($row.Cells[$i].AddParagraph((& $cellText $value))) if ($i -in $numeric) { $p.Format.Alignment = $right } if ($shown[$i] -in 'Impact', 'Severity', 'Orphaned', 'Empty' -and $severity.Contains([string]$value)) { $p.Format.Font.Color = $severity[[string]$value]; $p.Format.Font.Bold = $true } } } if ($rows.Count -gt $RowLimit) { $more = & $small ($section.AddParagraph("The first $RowLimit of $('{0:N0}' -f $rows.Count) rows - the HTML report and the CSV files have them all.")) $more.Format.Font.Color = $colors.Muted } } # --- 1. Summary -------------------------------------------------------------------------------------------- & $pdf.AddTitle "Azure environment assessment · 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] } } $factTable = & $pdf.NewTable @(4.5, ($pdf.PageWidth - 4.5)) 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.Subscriptions; Label = 'subscriptions' } @{ Value = '{0:N0}' -f $stats.ResourceGroups; Label = 'resource groups' } @{ Value = '{0:N0}' -f $stats.Resources; Label = 'resources' } @{ Value = '{0:N0}' -f $stats.ResourceTypes; Label = 'resource types' } @{ Value = '{0:N0}' -f $stats.Retirements; Label = 'retiring features'; Color = $(if ($stats.Retirements) { $pdf.Tone.Warn.Solid }) } if ($null -ne $stats.Advisor) { @{ Value = '{0:N0}' -f $stats.AdvisorHigh; Label = 'Advisor High'; Color = $(if ($stats.AdvisorHigh) { $pdf.Tone.Bad.Solid }) } } if ($null -ne $stats.Security) { @{ Value = '{0:N0}' -f $stats.SecurityHigh; Label = 'Defender High'; Color = $(if ($stats.SecurityHigh) { $pdf.Tone.Bad.Solid }) } } @{ Value = '{0:N0}' -f $stats.Orphans; Label = 'unattached / empty'; Color = $(if ($stats.Orphans) { $pdf.Tone.Warn.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) $value.Format.Alignment = [MigraDoc.DocumentObjectModel.ParagraphAlignment]::Center $value.Format.Font.Size = 16 $value.Format.Font.Name = 'Segoe UI Semibold' if ($tileData[$i].Contains('Color') -and $tileData[$i].Color) { $value.Format.Font.Color = $tileData[$i].Color } $caption = $cell.AddParagraph($tileData[$i].Label) $caption.Format.Alignment = [MigraDoc.DocumentObjectModel.ParagraphAlignment]::Center $caption.Format.Font.Size = 8 $caption.Format.Font.Color = $colors.Muted } # --- 2. Each category, each sheet ----------------------------------------------------------------------------- $categories = [System.Collections.Generic.List[string]]::new() foreach ($sheet in $Assessment.Sheets) { if (-not $categories.Contains($sheet.Category)) { $categories.Add($sheet.Category) } } foreach ($category in $categories) { $members = @($Assessment.Sheets | Where-Object { $_.Category -eq $category -and (@($_.Rows).Count -or $_.Error) }) if (-not $members.Count) { continue } $section.AddPageBreak() $section.AddParagraph($category, 'Heading1') | Out-Null foreach ($sheet in $members) { $section.AddParagraph("$($sheet.Sheet) ($('{0:N0}' -f @($sheet.Rows).Count))", 'Heading2') | Out-Null if ($sheet.Note) { (& $small ($section.AddParagraph($sheet.Note))).Format.Font.Color = $colors.Muted } if ($sheet.Error) { (& $small ($section.AddParagraph("Couldn't be read: $($sheet.Error)"))).Format.Font.Color = $pdf.Tone.Bad.Solid; continue } & $writeTable $sheet @($sheet.Key) } } Save-AACPdfDocument -Pdf $pdf -Path $Path } |