Public/Report/New-TBDocumentation.ps1
|
function New-TBDocumentation { <# .SYNOPSIS Generates tenant configuration monitoring documentation. .DESCRIPTION Collects monitoring data (monitors, baselines, snapshots, drifts) and generates a formatted document suitable for compliance review, knowledge sharing, or wiki embedding. .PARAMETER OutputPath The file path for the documentation output. Extension determines format (.html or .md). Defaults to TBDocumentation-{timestamp}.{html|md}. .PARAMETER Format Output format: HTML or Markdown. Defaults to HTML. If OutputPath has a recognized extension, that takes precedence. .PARAMETER IncludeDriftHistory Include a drift history section with summary breakdowns. .EXAMPLE New-TBDocumentation -OutputPath './docs/tenant-config.html' .EXAMPLE New-TBDocumentation -OutputPath './audit.md' -IncludeDriftHistory #> [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter()] [string]$OutputPath, [Parameter()] [ValidateSet('HTML', 'Markdown')] [string]$Format = 'HTML', [Parameter()] [switch]$IncludeDriftHistory ) # Determine format from file extension if provided if ($OutputPath) { $extension = [System.IO.Path]::GetExtension($OutputPath).ToLower() if ($extension -eq '.md') { $Format = 'Markdown' } elseif ($extension -eq '.html' -or $extension -eq '.htm') { $Format = 'HTML' } } if (-not $OutputPath) { $dateStamp = Get-Date -Format 'yyyyMMdd-HHmmss' $ext = if ($Format -eq 'Markdown') { '.md' } else { '.html' } $OutputPath = 'TBDocumentation-{0}{1}' -f $dateStamp, $ext } Write-TBLog -Message 'Collecting data for documentation' $monitors = @(Get-TBMonitor) # Collect baselines per monitor $baselines = @() foreach ($monitor in $monitors) { try { $bl = Get-TBBaseline -MonitorId $monitor.Id if ($bl) { $baselines += $bl } } catch { Write-TBLog -Message ('Could not retrieve baseline for monitor {0}: {1}' -f $monitor.Id, $_.Exception.Message) -Level 'Warning' } } $snapshots = @(Get-TBSnapshot) $driftSummary = $null if ($IncludeDriftHistory) { $driftSummary = Get-TBDriftSummary } $timestamp = (Get-Date).ToString('o') if ($PSCmdlet.ShouldProcess($OutputPath, 'Generate documentation')) { $parentDir = Split-Path -Path $OutputPath -Parent if ($parentDir -and -not (Test-Path -Path $parentDir)) { $null = New-Item -Path $parentDir -ItemType Directory -Force } $docParams = @{ Monitors = $monitors Baselines = $baselines Snapshots = $snapshots DriftSummary = $driftSummary GeneratedAt = $timestamp } if ($Format -eq 'Markdown') { $content = New-TBDocumentationMarkdown @docParams } else { $content = New-TBDocumentationHtml @docParams } $content | Out-File -FilePath $OutputPath -Encoding utf8 -Force Write-TBLog -Message ('Documentation generated: {0}' -f $OutputPath) [PSCustomObject]@{ OutputPath = (Resolve-Path -Path $OutputPath).Path Format = $Format MonitorCount = $monitors.Count BaselineCount = $baselines.Count SnapshotCount = $snapshots.Count GeneratedAt = $timestamp } } } function New-TBDocumentationHtml { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [array]$Monitors, [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [array]$Baselines, [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [array]$Snapshots, [Parameter()] $DriftSummary, [Parameter(Mandatory = $true)] [string]$GeneratedAt ) $styleTokens = Get-TBFluentHtmlStyleTokenSet # Build Table of Contents entries $tocEntries = @( '<li><a href="#executive-summary">Executive Summary</a></li>' '<li><a href="#monitor-inventory">Monitor Inventory</a></li>' '<li><a href="#baseline-details">Baseline Details</a></li>' '<li><a href="#snapshot-inventory">Snapshot Inventory</a></li>' ) if ($DriftSummary) { $tocEntries += '<li><a href="#drift-history">Drift History</a></li>' } $tocHtml = $tocEntries -join "`n " # Executive Summary $summaryHtml = @" <section id="executive-summary"> <h2>1. Executive Summary</h2> <div class="summary"> <div class="card"> <div class="label">Monitors</div> <div class="value">$($Monitors.Count)</div> </div> <div class="card"> <div class="label">Baselines</div> <div class="value">$($Baselines.Count)</div> </div> <div class="card"> <div class="label">Snapshots</div> <div class="value">$($Snapshots.Count)</div> </div> </div> </section> "@ # Monitor Inventory $monitorRows = '' foreach ($mon in $Monitors) { $mId = [System.Net.WebUtility]::HtmlEncode($mon.Id) $mName = '' $mDesc = '' $mStatus = '' $mFreq = '' if ($mon -is [hashtable]) { $mName = [System.Net.WebUtility]::HtmlEncode($mon['displayName']) $mDesc = [System.Net.WebUtility]::HtmlEncode($mon['description']) $mStatus = [System.Net.WebUtility]::HtmlEncode($mon['status']) $mFreq = $mon['monitorRunFrequencyInHours'] } else { if ($mon.PSObject.Properties['DisplayName']) { $mName = [System.Net.WebUtility]::HtmlEncode($mon.DisplayName) } if ($mon.PSObject.Properties['Description']) { $mDesc = [System.Net.WebUtility]::HtmlEncode($mon.Description) } if ($mon.PSObject.Properties['Status']) { $mStatus = [System.Net.WebUtility]::HtmlEncode($mon.Status) } if ($mon.PSObject.Properties['MonitorRunFrequencyInHours']) { $mFreq = $mon.MonitorRunFrequencyInHours } } $statusClass = if ($mStatus -eq 'active') { 'status-active' } else { 'status-other' } $monitorRows += @" <tr> <td>$mName</td> <td><code>$mId</code></td> <td><span class="badge $statusClass">$mStatus</span></td> <td>${mFreq}h</td> <td>$mDesc</td> </tr> "@ } if (-not $monitorRows) { $monitorRows = '<tr><td colspan="5" style="text-align:center;">No monitors configured.</td></tr>' } $monitorHtml = @" <section id="monitor-inventory"> <h2>2. Monitor Inventory</h2> <table> <thead> <tr> <th>Name</th> <th>ID</th> <th>Status</th> <th>Frequency</th> <th>Description</th> </tr> </thead> <tbody> $monitorRows </tbody> </table> </section> "@ # Baseline Details $baselineHtml = '<section id="baseline-details"><h2>3. Baseline Details</h2>' if ($Baselines.Count -eq 0) { $baselineHtml += '<p>No baselines found.</p>' } else { foreach ($bl in $Baselines) { $blName = '' $blDesc = '' $resources = @() if ($bl -is [hashtable]) { $blName = [System.Net.WebUtility]::HtmlEncode($bl['displayName']) $blDesc = [System.Net.WebUtility]::HtmlEncode($bl['description']) $resources = @($bl['resources']) } else { if ($bl.PSObject.Properties['DisplayName']) { $blName = [System.Net.WebUtility]::HtmlEncode($bl.DisplayName) } if ($bl.PSObject.Properties['Description']) { $blDesc = [System.Net.WebUtility]::HtmlEncode($bl.Description) } if ($bl.PSObject.Properties['Resources']) { $resources = @($bl.Resources) } } $baselineHtml += @" <div class="baseline-card"> <h3>$blName</h3> <p>$blDesc</p> "@ foreach ($res in $resources) { $resName = '' $resType = '' $properties = @{} if ($res -is [hashtable]) { $resName = [System.Net.WebUtility]::HtmlEncode($res['displayName']) $resType = [System.Net.WebUtility]::HtmlEncode($res['resourceType']) if ($res['properties']) { $properties = $res['properties'] } } else { if ($res.PSObject.Properties['displayName']) { $resName = [System.Net.WebUtility]::HtmlEncode($res.displayName) } if ($res.PSObject.Properties['resourceType']) { $resType = [System.Net.WebUtility]::HtmlEncode($res.resourceType) } if ($res.PSObject.Properties['properties']) { $properties = $res.properties } } $baselineHtml += @" <h4>$resName <small>($resType)</small></h4> <table class="props-table"> <thead><tr><th>Property</th><th>Value</th></tr></thead> <tbody> "@ $propsList = @() if ($properties -is [hashtable]) { foreach ($key in $properties.Keys) { $propsList += @{ Name = $key; Value = "$($properties[$key])" } } } elseif ($properties) { foreach ($prop in $properties.PSObject.Properties) { $propsList += @{ Name = $prop.Name; Value = "$($prop.Value)" } } } if ($propsList.Count -eq 0) { $baselineHtml += '<tr><td colspan="2" style="text-align:center;">No properties defined.</td></tr>' } else { foreach ($p in $propsList) { $pName = [System.Net.WebUtility]::HtmlEncode($p.Name) $pVal = [System.Net.WebUtility]::HtmlEncode($p.Value) $baselineHtml += " <tr><td>$pName</td><td><code>$pVal</code></td></tr>`n" } } $baselineHtml += @" </tbody> </table> "@ } $baselineHtml += ' </div>' } } $baselineHtml += '</section>' # Snapshot Inventory $snapshotRows = '' foreach ($snap in $Snapshots) { $sName = '' $sId = '' $sStatus = '' $sCreated = '' $sCompleted = '' $sResources = '' if ($snap -is [hashtable]) { $sName = [System.Net.WebUtility]::HtmlEncode($snap['displayName']) $sId = [System.Net.WebUtility]::HtmlEncode($snap['id']) $sStatus = [System.Net.WebUtility]::HtmlEncode($snap['status']) $sCreated = [System.Net.WebUtility]::HtmlEncode($snap['createdDateTime']) $sCompleted = if ($snap['completedDateTime']) { [System.Net.WebUtility]::HtmlEncode($snap['completedDateTime']) } else { '-' } if ($snap['resources']) { $sResources = [System.Net.WebUtility]::HtmlEncode(($snap['resources'] -join ', ')) } } else { if ($snap.PSObject.Properties['DisplayName']) { $sName = [System.Net.WebUtility]::HtmlEncode($snap.DisplayName) } if ($snap.PSObject.Properties['Id']) { $sId = [System.Net.WebUtility]::HtmlEncode($snap.Id) } if ($snap.PSObject.Properties['Status']) { $sStatus = [System.Net.WebUtility]::HtmlEncode($snap.Status) } if ($snap.PSObject.Properties['CreatedDateTime']) { $sCreated = [System.Net.WebUtility]::HtmlEncode("$($snap.CreatedDateTime)") } $sCompleted = '-' if ($snap.PSObject.Properties['CompletedDateTime'] -and $snap.CompletedDateTime) { $sCompleted = [System.Net.WebUtility]::HtmlEncode("$($snap.CompletedDateTime)") } if ($snap.PSObject.Properties['Resources']) { $sResources = [System.Net.WebUtility]::HtmlEncode(($snap.Resources -join ', ')) } } $snapshotRows += @" <tr> <td>$sName</td> <td><code>$sId</code></td> <td>$sStatus</td> <td>$sCreated</td> <td>$sCompleted</td> <td>$sResources</td> </tr> "@ } if (-not $snapshotRows) { $snapshotRows = '<tr><td colspan="6" style="text-align:center;">No snapshots found.</td></tr>' } $snapshotHtml = @" <section id="snapshot-inventory"> <h2>4. Snapshot Inventory</h2> <table> <thead> <tr> <th>Name</th> <th>ID</th> <th>Status</th> <th>Created</th> <th>Completed</th> <th>Resources</th> </tr> </thead> <tbody> $snapshotRows </tbody> </table> </section> "@ # Drift History (optional) $driftHtml = '' if ($DriftSummary) { $totalDrifts = 0 $totalProps = 0 if ($DriftSummary -is [hashtable]) { $totalDrifts = $DriftSummary['TotalDrifts'] $totalProps = $DriftSummary['TotalDriftedProperties'] } else { if ($DriftSummary.PSObject.Properties['TotalDrifts']) { $totalDrifts = $DriftSummary.TotalDrifts } if ($DriftSummary.PSObject.Properties['TotalDriftedProperties']) { $totalProps = $DriftSummary.TotalDriftedProperties } } $statusRows = '' $byStatus = $null if ($DriftSummary -is [hashtable]) { $byStatus = $DriftSummary['ByStatus'] } elseif ($DriftSummary.PSObject.Properties['ByStatus']) { $byStatus = $DriftSummary.ByStatus } if ($byStatus) { if ($byStatus -is [hashtable]) { foreach ($key in $byStatus.Keys) { $statusRows += ('<tr><td>{0}</td><td>{1}</td></tr>' -f [System.Net.WebUtility]::HtmlEncode($key), $byStatus[$key]) } } else { foreach ($prop in $byStatus.PSObject.Properties) { $statusRows += ('<tr><td>{0}</td><td>{1}</td></tr>' -f [System.Net.WebUtility]::HtmlEncode($prop.Name), $prop.Value) } } } if (-not $statusRows) { $statusRows = '<tr><td colspan="2" style="text-align:center;">No drift data.</td></tr>' } $resourceRows = '' $byResourceType = $null if ($DriftSummary -is [hashtable]) { $byResourceType = $DriftSummary['ByResourceType'] } elseif ($DriftSummary.PSObject.Properties['ByResourceType']) { $byResourceType = $DriftSummary.ByResourceType } if ($byResourceType) { if ($byResourceType -is [hashtable]) { foreach ($key in $byResourceType.Keys) { $resourceRows += ('<tr><td>{0}</td><td>{1}</td></tr>' -f [System.Net.WebUtility]::HtmlEncode($key), $byResourceType[$key]) } } else { foreach ($prop in $byResourceType.PSObject.Properties) { $resourceRows += ('<tr><td>{0}</td><td>{1}</td></tr>' -f [System.Net.WebUtility]::HtmlEncode($prop.Name), $prop.Value) } } } if (-not $resourceRows) { $resourceRows = '<tr><td colspan="2" style="text-align:center;">No drift data.</td></tr>' } $driftSectionNum = '5' $alertClass = if ($totalDrifts -gt 0) { ' alert' } else { '' } $driftHtml = @" <section id="drift-history"> <h2>$driftSectionNum. Drift History</h2> <div class="summary"> <div class="card$alertClass"> <div class="label">Total Drifts</div> <div class="value">$totalDrifts</div> </div> <div class="card"> <div class="label">Drifted Properties</div> <div class="value">$totalProps</div> </div> </div> <h3>By Status</h3> <table class="compact-table"> <thead><tr><th>Status</th><th>Count</th></tr></thead> <tbody>$statusRows</tbody> </table> <h3>By Resource Type</h3> <table class="compact-table"> <thead><tr><th>Resource Type</th><th>Count</th></tr></thead> <tbody>$resourceRows</tbody> </table> </section> "@ } $html = @" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Tenant Configuration Monitoring Documentation</title> <style> $styleTokens body { margin: 2rem auto; max-width: 1120px; } h1 { color: var(--tb-accent); border-bottom: 2px solid var(--tb-accent); padding-bottom: 0.5rem; } h2 { color: var(--tb-text); margin-top: 2rem; border-bottom: 1px solid var(--tb-border); padding-bottom: 0.3rem; } h3 { color: var(--tb-text-muted); margin-top: 1.5rem; } h4 { color: var(--tb-text); margin-top: 1rem; } h4 small { color: var(--tb-text-muted); font-weight: normal; } nav { border-radius: 8px; padding: 1rem 1.5rem; margin: 1rem 0; } nav ol { margin: 0; padding-left: 1.5rem; } nav li { margin: 0.3rem 0; } nav a { color: var(--tb-accent); text-decoration: none; } nav a:hover { text-decoration: underline; } .summary { display: flex; gap: 1rem; flex-wrap: wrap; margin: 1rem 0; } .card { border-radius: 8px; padding: 1rem 1.5rem; min-width: 150px; } .card .label { font-size: 0.85rem; color: var(--tb-text-muted); text-transform: uppercase; letter-spacing: 0.5px; } .card .value { font-size: 2rem; font-weight: 600; color: var(--tb-text); } .card.alert .value { color: var(--tb-danger); } table { width: 100%; border-collapse: collapse; margin: 1rem 0; border-radius: 8px; overflow: hidden; } th { background: var(--tb-accent); color: #fff; text-align: left; padding: 0.75rem 1rem; font-weight: 600; } td { padding: 0.5rem 1rem; border-bottom: 1px solid var(--tb-border); } tr:hover td { background: var(--tb-surface-muted); } code { background: var(--tb-surface-muted); padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.9em; } .badge { padding: 0.2rem 0.6rem; border-radius: 3px; font-size: 0.85rem; font-weight: 600; } .status-active { background: #dff6dd; color: var(--tb-success); } .status-other { background: var(--tb-surface-muted); color: var(--tb-text-muted); } .baseline-card { border-radius: 8px; padding: 1rem 1.5rem; margin: 1rem 0; } .props-table { max-width: 600px; } .compact-table { max-width: 400px; } .footer { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--tb-border); color: var(--tb-text-muted); font-size: 0.85rem; } @media print { body { margin: 0; background: #fff; } .card { box-shadow: none; border: 1px solid #ccc; } table { box-shadow: none; } nav a { color: #000; } } </style> </head> <body> <h1>Tenant Configuration Monitoring Documentation</h1> <p>Generated: $([System.Net.WebUtility]::HtmlEncode($GeneratedAt))</p> <nav> <strong>Table of Contents</strong> <ol> $tocHtml </ol> </nav> $summaryHtml $monitorHtml $baselineHtml $snapshotHtml $driftHtml <div class="footer"> <p>Generated by TenantBaseline PowerShell Module</p> </div> </body> </html> "@ return $html } function New-TBDocumentationMarkdown { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [array]$Monitors, [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [array]$Baselines, [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [array]$Snapshots, [Parameter()] $DriftSummary, [Parameter(Mandatory = $true)] [string]$GeneratedAt ) $lines = [System.Collections.ArrayList]::new() $null = $lines.Add('# Tenant Configuration Monitoring Documentation') $null = $lines.Add('') $null = $lines.Add(('Generated: {0}' -f $GeneratedAt)) $null = $lines.Add('') # Table of Contents $null = $lines.Add('## Table of Contents') $null = $lines.Add('') $null = $lines.Add('1. [Executive Summary](#executive-summary)') $null = $lines.Add('2. [Monitor Inventory](#monitor-inventory)') $null = $lines.Add('3. [Baseline Details](#baseline-details)') $null = $lines.Add('4. [Snapshot Inventory](#snapshot-inventory)') if ($DriftSummary) { $null = $lines.Add('5. [Drift History](#drift-history)') } $null = $lines.Add('') # Executive Summary $null = $lines.Add('## Executive Summary') $null = $lines.Add('') $null = $lines.Add(('- **Monitors**: {0}' -f $Monitors.Count)) $null = $lines.Add(('- **Baselines**: {0}' -f $Baselines.Count)) $null = $lines.Add(('- **Snapshots**: {0}' -f $Snapshots.Count)) $null = $lines.Add('') # Monitor Inventory $null = $lines.Add('## Monitor Inventory') $null = $lines.Add('') if ($Monitors.Count -eq 0) { $null = $lines.Add('No monitors configured.') } else { $null = $lines.Add('| Name | ID | Status | Frequency | Description |') $null = $lines.Add('|------|-----|--------|-----------|-------------|') foreach ($mon in $Monitors) { $mName = '' $mId = '' $mStatus = '' $mFreq = '' $mDesc = '' if ($mon -is [hashtable]) { $mName = $mon['displayName'] $mId = $mon['id'] $mStatus = $mon['status'] $mFreq = $mon['monitorRunFrequencyInHours'] $mDesc = $mon['description'] } else { if ($mon.PSObject.Properties['DisplayName']) { $mName = $mon.DisplayName } if ($mon.PSObject.Properties['Id']) { $mId = $mon.Id } if ($mon.PSObject.Properties['Status']) { $mStatus = $mon.Status } if ($mon.PSObject.Properties['MonitorRunFrequencyInHours']) { $mFreq = $mon.MonitorRunFrequencyInHours } if ($mon.PSObject.Properties['Description']) { $mDesc = $mon.Description } } $null = $lines.Add(('| {0} | `{1}` | {2} | {3}h | {4} |' -f $mName, $mId, $mStatus, $mFreq, $mDesc)) } } $null = $lines.Add('') # Baseline Details $null = $lines.Add('## Baseline Details') $null = $lines.Add('') if ($Baselines.Count -eq 0) { $null = $lines.Add('No baselines found.') } else { foreach ($bl in $Baselines) { $blName = '' $blDesc = '' $resources = @() if ($bl -is [hashtable]) { $blName = $bl['displayName'] $blDesc = $bl['description'] $resources = @($bl['resources']) } else { if ($bl.PSObject.Properties['DisplayName']) { $blName = $bl.DisplayName } if ($bl.PSObject.Properties['Description']) { $blDesc = $bl.Description } if ($bl.PSObject.Properties['Resources']) { $resources = @($bl.Resources) } } $null = $lines.Add(('### {0}' -f $blName)) $null = $lines.Add('') if ($blDesc) { $null = $lines.Add($blDesc) $null = $lines.Add('') } foreach ($res in $resources) { $resName = '' $resType = '' $properties = @{} if ($res -is [hashtable]) { $resName = $res['displayName'] $resType = $res['resourceType'] if ($res['properties']) { $properties = $res['properties'] } } else { if ($res.PSObject.Properties['displayName']) { $resName = $res.displayName } if ($res.PSObject.Properties['resourceType']) { $resType = $res.resourceType } if ($res.PSObject.Properties['properties']) { $properties = $res.properties } } $null = $lines.Add(('#### {0} ({1})' -f $resName, $resType)) $null = $lines.Add('') $null = $lines.Add('| Property | Value |') $null = $lines.Add('|----------|-------|') $propsList = @() if ($properties -is [hashtable]) { foreach ($key in $properties.Keys) { $propsList += @{ Name = $key; Value = "$($properties[$key])" } } } elseif ($properties) { foreach ($prop in $properties.PSObject.Properties) { $propsList += @{ Name = $prop.Name; Value = "$($prop.Value)" } } } if ($propsList.Count -eq 0) { $null = $lines.Add('| (none) | - |') } else { foreach ($p in $propsList) { $null = $lines.Add(('| {0} | `{1}` |' -f $p.Name, $p.Value)) } } $null = $lines.Add('') } } } # Snapshot Inventory $null = $lines.Add('## Snapshot Inventory') $null = $lines.Add('') if ($Snapshots.Count -eq 0) { $null = $lines.Add('No snapshots found.') } else { $null = $lines.Add('| Name | ID | Status | Created | Completed | Resources |') $null = $lines.Add('|------|-----|--------|---------|-----------|-----------|') foreach ($snap in $Snapshots) { $sName = '' $sId = '' $sStatus = '' $sCreated = '' $sCompleted = '-' $sResources = '' if ($snap -is [hashtable]) { $sName = $snap['displayName'] $sId = $snap['id'] $sStatus = $snap['status'] $sCreated = $snap['createdDateTime'] if ($snap['completedDateTime']) { $sCompleted = $snap['completedDateTime'] } if ($snap['resources']) { $sResources = ($snap['resources'] -join ', ') } } else { if ($snap.PSObject.Properties['DisplayName']) { $sName = $snap.DisplayName } if ($snap.PSObject.Properties['Id']) { $sId = $snap.Id } if ($snap.PSObject.Properties['Status']) { $sStatus = $snap.Status } if ($snap.PSObject.Properties['CreatedDateTime']) { $sCreated = "$($snap.CreatedDateTime)" } if ($snap.PSObject.Properties['CompletedDateTime'] -and $snap.CompletedDateTime) { $sCompleted = "$($snap.CompletedDateTime)" } if ($snap.PSObject.Properties['Resources']) { $sResources = ($snap.Resources -join ', ') } } $null = $lines.Add(('| {0} | `{1}` | {2} | {3} | {4} | {5} |' -f $sName, $sId, $sStatus, $sCreated, $sCompleted, $sResources)) } } $null = $lines.Add('') # Drift History (optional) if ($DriftSummary) { $totalDrifts = 0 $totalProps = 0 if ($DriftSummary -is [hashtable]) { $totalDrifts = $DriftSummary['TotalDrifts'] $totalProps = $DriftSummary['TotalDriftedProperties'] } else { if ($DriftSummary.PSObject.Properties['TotalDrifts']) { $totalDrifts = $DriftSummary.TotalDrifts } if ($DriftSummary.PSObject.Properties['TotalDriftedProperties']) { $totalProps = $DriftSummary.TotalDriftedProperties } } $null = $lines.Add('## Drift History') $null = $lines.Add('') $null = $lines.Add(('- **Total Drifts**: {0}' -f $totalDrifts)) $null = $lines.Add(('- **Total Drifted Properties**: {0}' -f $totalProps)) $null = $lines.Add('') $null = $lines.Add('### By Status') $null = $lines.Add('') $null = $lines.Add('| Status | Count |') $null = $lines.Add('|--------|-------|') $byStatus = $null if ($DriftSummary -is [hashtable]) { $byStatus = $DriftSummary['ByStatus'] } elseif ($DriftSummary.PSObject.Properties['ByStatus']) { $byStatus = $DriftSummary.ByStatus } if ($byStatus) { if ($byStatus -is [hashtable]) { foreach ($key in $byStatus.Keys) { $null = $lines.Add(('| {0} | {1} |' -f $key, $byStatus[$key])) } } else { foreach ($prop in $byStatus.PSObject.Properties) { $null = $lines.Add(('| {0} | {1} |' -f $prop.Name, $prop.Value)) } } } $null = $lines.Add('') $null = $lines.Add('### By Resource Type') $null = $lines.Add('') $null = $lines.Add('| Resource Type | Count |') $null = $lines.Add('|---------------|-------|') $byResourceType = $null if ($DriftSummary -is [hashtable]) { $byResourceType = $DriftSummary['ByResourceType'] } elseif ($DriftSummary.PSObject.Properties['ByResourceType']) { $byResourceType = $DriftSummary.ByResourceType } if ($byResourceType) { if ($byResourceType -is [hashtable]) { foreach ($key in $byResourceType.Keys) { $null = $lines.Add(('| {0} | {1} |' -f $key, $byResourceType[$key])) } } else { foreach ($prop in $byResourceType.PSObject.Properties) { $null = $lines.Add(('| {0} | {1} |' -f $prop.Name, $prop.Value)) } } } $null = $lines.Add('') } $null = $lines.Add('---') $null = $lines.Add('') $null = $lines.Add('*Generated by TenantBaseline PowerShell Module*') return ($lines -join "`n") } |