Private/Get-SPMDeltaHtml.ps1
|
function Get-SPMDeltaHtml { <# .SYNOPSIS Renders a delta object (from Compare-SPPermissionScan) as a self-contained HTML page. #> [CmdletBinding()] param( [Parameter(Mandatory)] $Delta ) $e = { param($s) [System.Net.WebUtility]::HtmlEncode([string]$s) } $sb = [System.Text.StringBuilder]::new() $null = $sb.AppendLine(@' <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>SharePoint Permission Delta</title> <style> :root { --border:#e2e8f0; --dim:#64748b; --text:#1e293b; } * { box-sizing:border-box; } body { margin:0; font:14px/1.5 "Segoe UI Variable Text","Segoe UI",system-ui,sans-serif; color:var(--text); background:#f8fafc; } header { background:linear-gradient(135deg,#0b1f33 0%,#16344f 45%,#1f4e79 100%); color:#fff; padding:20px 26px 18px; position:relative; box-shadow:0 2px 18px rgba(11,31,51,.35); } header::after { content:''; position:absolute; left:0; right:0; bottom:0; height:3px; background:linear-gradient(90deg,#5eead4,#38bdf8,#818cf8); opacity:.9; } .kicker { font-size:11px; text-transform:uppercase; letter-spacing:.18em; color:#7dd3fc; font-weight:600; margin-bottom:2px; } h1 { margin:0; font-size:22px; font-weight:650; } .meta { margin-top:9px; display:flex; gap:8px; flex-wrap:wrap; } .mchip { font-size:11.5px; color:rgba(255,255,255,.88); background:rgba(255,255,255,.08); border:1px solid rgba(255,255,255,.17); border-radius:999px; padding:3px 11px; } main { padding:18px 26px; max-width:1100px; } h2 { font-size:17px; margin:22px 0 4px; } h3 { font-size:13px; margin:16px 0 6px; text-transform:uppercase; letter-spacing:.05em; color:var(--dim); } .url { font-size:12px; color:var(--dim); } table { border-collapse:collapse; background:#fff; border:1px solid var(--border); width:100%; margin-top:4px; } th, td { padding:6px 12px; border-bottom:1px solid var(--border); text-align:left; font-size:13px; vertical-align:top; } th { background:#f1f5f9; font-size:11px; text-transform:uppercase; letter-spacing:.04em; color:var(--dim); } tr.add td { background:#f0fdf4; } tr.add td:first-child { box-shadow:inset 3px 0 0 #16a34a; } tr.rem td { background:#fef2f2; } tr.rem td:first-child { box-shadow:inset 3px 0 0 #dc2626; } tr.chg td { background:#fffbeb; } tr.chg td:first-child { box-shadow:inset 3px 0 0 #d97706; } .ok { padding:16px; background:#f0fdf4; border:1px solid #bbf7d0; border-radius:8px; color:#166534; margin-top:8px; } .chips { display:flex; gap:8px; flex-wrap:wrap; margin:8px 0 4px; } .chip { background:#fff; border:1px solid var(--border); border-radius:999px; padding:3px 12px; font-size:12px; color:var(--dim); } .chip b { color:var(--text); } code { font-size:11px; background:#f1f5f9; padding:1px 5px; border-radius:4px; } .dim { color:var(--dim); } </style> </head> <body> <header> <div class="kicker">SPPermissionMatrix · Delta Report</div> <h1>SharePoint Permission Delta</h1> <div class="meta"> '@) $null = $sb.AppendLine(' <span class="mchip">Reference: scanned ' + (& $e $Delta.reference.scanDate) + '</span>') $null = $sb.AppendLine(' <span class="mchip">Current: scanned ' + (& $e $Delta.difference.scanDate) + '</span>') $null = $sb.AppendLine(' <span class="mchip">' + [int]$Delta.totalChanges + ' change(s)</span>') $null = $sb.AppendLine(' <span class="mchip">Generated ' + (& $e $Delta.generated) + '</span>') $null = $sb.AppendLine(' </div></header><main>') if ([int]$Delta.totalChanges -eq 0) { $null = $sb.AppendLine('<div class="ok">No permission changes detected between the two scans.</div>') } $addTable = { param([string]$Title, [object[]]$Rows, [string[]]$Headers, [scriptblock]$CellsOf, [string]$RowClass) if (-not $Rows -or @($Rows).Count -eq 0) { return } $null = $sb.AppendLine('<h3>' + (& $e $Title) + ' (' + @($Rows).Count + ')</h3><table><thead><tr>') foreach ($h in $Headers) { $null = $sb.Append('<th>' + (& $e $h) + '</th>') } $null = $sb.AppendLine('</tr></thead><tbody>') foreach ($r in @($Rows)) { $null = $sb.Append('<tr class="' + $RowClass + '">') foreach ($cell in @(& $CellsOf $r)) { $null = $sb.Append('<td>' + $cell + '</td>') } $null = $sb.AppendLine('</tr>') } $null = $sb.AppendLine('</tbody></table>') } foreach ($site in @($Delta.sites)) { $null = $sb.AppendLine('<h2>' + (& $e $site.title) + '</h2><div class="url">' + (& $e $site.url) + '</div>') if ($site.status -eq 'added') { $null = $sb.AppendLine('<div class="ok" style="background:#f0fdf4">Site is new in the current scan.</div>'); continue } if ($site.status -eq 'removed') { $null = $sb.AppendLine('<div class="ok" style="background:#fef2f2;border-color:#fecaca;color:#991b1b">Site is no longer present in the current scan.</div>'); continue } if ([int]$site.summary.total -eq 0) { $null = $sb.AppendLine('<div class="ok">No changes on this site.</div>'); continue } $null = $sb.AppendLine('<div class="chips"><span class="chip">changes <b>' + [int]$site.summary.total + '</b></span></div>') & $addTable 'New inheritance breaks' $site.newBreaks @('Where', 'Unique permissions now assigned') { param($r) @((& $e $r.node), (& $e $r.detail)) } 'chg' & $addTable 'Inheritance restored' $site.restoredInheritance @('Where') { param($r) @((& $e $r.node)) } 'chg' & $addTable 'Added permissions' $site.addedAssignments @('Where', 'Principal', 'Level') { param($r) @((& $e $r.node), (& $e $r.principal), (& $e $r.permission)) } 'add' & $addTable 'Removed permissions' $site.removedAssignments @('Where', 'Principal', 'Level') { param($r) @((& $e $r.node), (& $e $r.principal), (& $e $r.permission)) } 'rem' & $addTable 'Changed levels' $site.changedAssignments @('Where', 'Principal', 'From', 'To') { param($r) @((& $e $r.node), (& $e $r.principal), (& $e $r.from), (& $e $r.to)) } 'chg' & $addTable 'New rows (folders/libraries)' $site.addedNodes @('Title', 'Path') { param($r) @((& $e $r.title), '<code>' + (& $e $r.url) + '</code>') } 'add' & $addTable 'Removed rows (folders/libraries)' $site.removedNodes @('Title', 'Path') { param($r) @((& $e $r.title), '<code>' + (& $e $r.url) + '</code>') } 'rem' & $addTable 'New sharing links' $site.addedSharingLinks @('Link', 'People') { param($r) @((& $e $r.kind), [string][int]$r.people) } 'add' & $addTable 'Removed sharing links' $site.removedSharingLinks @('Link') { param($r) @((& $e $r.kind)) } 'rem' & $addTable 'Group membership changes' $site.membershipChanges @('Group', 'Added people', 'Removed people') { param($r) @((& $e $r.group), ($(if (@($r.added).Count) { (& $e (@($r.added) -join ', ')) } else { '<span class="dim">–</span>' })), ($(if (@($r.removed).Count) { (& $e (@($r.removed) -join ', ')) } else { '<span class="dim">–</span>' }))) } 'chg' } $null = $sb.AppendLine('<p class="dim" style="margin-top:20px;font-size:12px">Assignment changes are reported where they are assigned (nodes with unique permissions); child rows inheriting from them change implicitly. Limited-access noise is filtered on both sides.</p>') $null = $sb.AppendLine('</main></body></html>') return $sb.ToString() } |