Private/New-AACPdfDocument.ps1
|
function New-AACPdfDocument { <# .SYNOPSIS Starts an A4 PDF document in the module's report style, and returns it with the helpers the report commands build their pages with. .DESCRIPTION Shared by every PDF report (Pester, firewall, Advisor) so every PDF the module writes looks the same: Segoe UI, a blue accent, headings, a header line with the title and date, and a footer with "Page X of Y". Loads the PDF libraries first (Import-AACPdfLibrary checks every DLL's pinned SHA-256 before loading it). The returned object carries: Document, Section the MigraDoc document and its one section PageWidth usable width in cm (17.4 portrait, 26.1 landscape) Colors Ink, Muted, Rule, Panel, Accent, White, Amber Tone Good / Bad / Neutral / Warn, each with Text, Fill (light background) and Solid colors Generated when the document was started Cm, Pt, Color unit and color helpers: & $pdf.Cm 2.5 NewTable & $pdf.NewTable @(widths in cm) - a borderless table AddHeaderRow & $pdf.AddHeaderRow $table @(labels) @(right-aligned column indexes) AddBodyRow & $pdf.AddBodyRow $table - a row with a thin rule under it AddTitle & $pdf.AddTitle 'Subtitle text' - the big title block Write it with Save-AACPdfDocument. #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Builds a document in memory; nothing outside the process changes.')] param( [Parameter(Mandatory)] [string] $Title, [string] $Subject, [switch] $Landscape ) Import-AACPdfLibrary $cm = { param([double] $Value) [MigraDoc.DocumentObjectModel.Unit]::FromCentimeter($Value) } $pt = { param([double] $Value) [MigraDoc.DocumentObjectModel.Unit]::FromPoint($Value) } $color = { param([string] $Hex) [MigraDoc.DocumentObjectModel.Color]::new([Convert]::ToByte($Hex.Substring(1, 2), 16), [Convert]::ToByte($Hex.Substring(3, 2), 16), [Convert]::ToByte($Hex.Substring(5, 2), 16)) } $colors = @{ Ink = & $color '#1F2937' Muted = & $color '#6B7280' Rule = & $color '#E5E7EB' Panel = & $color '#F3F4F6' Accent = & $color '#0F4C81' White = & $color '#FFFFFF' Amber = & $color '#CA8A04' } $tone = @{ Good = @{ Text = (& $color '#166534'); Fill = (& $color '#DCFCE7'); Solid = (& $color '#16A34A') } Bad = @{ Text = (& $color '#991B1B'); Fill = (& $color '#FEE2E2'); Solid = (& $color '#DC2626') } Neutral = @{ Text = (& $color '#374151'); Fill = (& $color '#E5E7EB'); Solid = (& $color '#9CA3AF') } Warn = @{ Text = (& $color '#854D0E'); Fill = (& $color '#FEF9C3'); Solid = (& $color '#CA8A04') } } # A4 is 21 x 29.7 cm; 1.8 cm margins either side. $pageWidth = if ($Landscape) { 26.1 } else { 17.4 } $generated = Get-Date $doc = [MigraDoc.DocumentObjectModel.Document]::new() $doc.Info.Title = $Title $doc.Info.Author = 'Azure Admin Console' if ($Subject) { $doc.Info.Subject = $Subject } $normal = $doc.Styles['Normal'] $normal.Font.Name = 'Segoe UI' $normal.Font.Size = 9 $normal.Font.Color = $colors.Ink foreach ($level in @(@{ Name = 'Heading1'; Size = 15; Before = 0; After = 8 }, @{ Name = 'Heading2'; Size = 12; Before = 16; After = 4 }, @{ Name = 'Heading3'; Size = 10; Before = 10; After = 4 })) { $heading = $doc.Styles[$level.Name] $heading.Font.Name = 'Segoe UI Semibold' $heading.Font.Size = $level.Size $heading.Font.Bold = $false $heading.Font.Color = $colors.Accent $heading.ParagraphFormat.SpaceBefore = & $pt $level.Before $heading.ParagraphFormat.SpaceAfter = & $pt $level.After $heading.ParagraphFormat.KeepWithNext = $true } $section = $doc.AddSection() $section.PageSetup = $doc.DefaultPageSetup.Clone() # The page size is set explicitly: the cloned default setup carries fixed # portrait dimensions, which win over PageFormat/Orientation. $section.PageSetup.PageFormat = [MigraDoc.DocumentObjectModel.PageFormat]::A4 $section.PageSetup.Orientation = [MigraDoc.DocumentObjectModel.Orientation]::Portrait $section.PageSetup.PageWidth = & $cm $(if ($Landscape) { 29.7 } else { 21.0 }) $section.PageSetup.PageHeight = & $cm $(if ($Landscape) { 21.0 } else { 29.7 }) $section.PageSetup.TopMargin = & $cm 2.2 $section.PageSetup.BottomMargin = & $cm 1.8 $section.PageSetup.LeftMargin = & $cm 1.8 $section.PageSetup.RightMargin = & $cm 1.8 $section.PageSetup.HeaderDistance = & $cm 1.0 $section.PageSetup.FooterDistance = & $cm 0.8 $header = $section.Headers.Primary.AddParagraph() $header.Format.Font.Size = 8 $header.Format.Font.Color = $colors.Muted $header.Format.AddTabStop((& $cm $pageWidth), [MigraDoc.DocumentObjectModel.TabAlignment]::Right) | Out-Null $header.Format.Borders.Bottom.Width = 0.5 $header.Format.Borders.Bottom.Color = $colors.Rule $header.Format.Borders.DistanceFromBottom = & $pt 3 $header.AddText($Title) | Out-Null $header.AddTab() | Out-Null $header.AddText($generated.ToString('d MMM yyyy HH:mm')) | Out-Null $footer = $section.Footers.Primary.AddParagraph() $footer.Format.Font.Size = 8 $footer.Format.Font.Color = $colors.Muted $footer.Format.AddTabStop((& $cm $pageWidth), [MigraDoc.DocumentObjectModel.TabAlignment]::Right) | Out-Null $footer.AddText('Generated by Azure Admin Console') | Out-Null $footer.AddTab() | Out-Null $footer.AddText('Page ') | Out-Null $footer.AddPageField() | Out-Null $footer.AddText(' of ') | Out-Null $footer.AddNumPagesField() | Out-Null # The helpers are closures, so they keep working after this function # returns (they capture $section, $cm, $pt and $colors). $newTable = { param([double[]] $Widths) $table = $section.AddTable() $table.Borders.Width = 0 $table.TopPadding = & $pt 3 $table.BottomPadding = & $pt 3 $table.LeftPadding = & $pt 4 $table.RightPadding = & $pt 4 foreach ($width in $Widths) { $table.AddColumn((& $cm $width)) | Out-Null } $table }.GetNewClosure() $addHeaderRow = { param($Table, [string[]] $Labels, [int[]] $RightAligned = @()) $row = $Table.AddRow() $row.HeadingFormat = $true $row.Shading.Color = $colors.Panel $row.Format.Font.Size = 8 $row.Format.Font.Color = $colors.Muted $row.Borders.Bottom.Width = 0.75 $row.Borders.Bottom.Color = $colors.Rule for ($i = 0; $i -lt $Labels.Count; $i++) { $row.Cells[$i].AddParagraph($Labels[$i]) | Out-Null if ($i -in $RightAligned) { $row.Cells[$i].Format.Alignment = [MigraDoc.DocumentObjectModel.ParagraphAlignment]::Right } } }.GetNewClosure() $addBodyRow = { param($Table) $row = $Table.AddRow() $row.Borders.Bottom.Width = 0.5 $row.Borders.Bottom.Color = $colors.Rule $row.VerticalAlignment = [MigraDoc.DocumentObjectModel.Tables.VerticalAlignment]::Top $row }.GetNewClosure() $addTitle = { param([string] $Subtitle) $titleParagraph = $section.AddParagraph($Title) $titleParagraph.Format.Font.Name = 'Segoe UI Semibold' $titleParagraph.Format.Font.Size = 22 $titleParagraph.Format.Font.Color = $colors.Accent $titleParagraph.Format.SpaceAfter = & $pt 2 if ($Subtitle) { $subtitleParagraph = $section.AddParagraph($Subtitle) $subtitleParagraph.Format.Font.Color = $colors.Muted $subtitleParagraph.Format.SpaceAfter = & $pt 14 } }.GetNewClosure() [pscustomobject]@{ Document = $doc Section = $section PageWidth = $pageWidth Colors = $colors Tone = $tone Generated = $generated Cm = $cm Pt = $pt Color = $color NewTable = $newTable AddHeaderRow = $addHeaderRow AddBodyRow = $addBodyRow AddTitle = $addTitle } } |