Public/Export-SharingLinkReport.ps1

function Export-SharingLinkReport {
    <#
    .SYNOPSIS
        Write Get-SharingLink results to CSV or a standalone HTML report.

    .DESCRIPTION
        CSV for the record and for feeding other tools; HTML for the version you
        email to whoever asked "are we overshared". The HTML is self-contained -
        no external CSS or scripts - so it opens anywhere and survives being
        forwarded.

    .EXAMPLE
        Get-SharingLink -SiteUrl $url -UseExistingConnection |
            Export-SharingLinkReport -Path .\sharing.html -Html

    .EXAMPLE
        Get-SharingLink -Tenant -TenantAdminUrl $admin -ClientId $id -Interactive |
            Export-SharingLinkReport -Path .\sharing.csv
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [psobject] $Link,

        [Parameter(Mandatory)]
        [string]   $Path,

        [switch]   $Html
    )

    begin { $rows = [System.Collections.Generic.List[object]]::new() }
    process { $rows.Add($Link) }
    end {
        if ($rows.Count -eq 0) {
            Write-Warning "Nothing to export - Get-SharingLink returned no links."
            return
        }

        if (-not $Html) {
            $rows | Export-Csv -Path $Path -NoTypeInformation -Encoding UTF8
            $file = Get-Item $Path
            # Visible by default (-InformationAction Continue), PSSA-clean, and
            # still returns the FileInfo so it can be piped.
            Write-Information "Report generated: $($rows.Count) link(s) written to $($file.FullName)" -InformationAction Continue
            return $file
        }

        # --- self-contained HTML ------------------------------------------------
        Add-Type -AssemblyName System.Web   # needed before HtmlEncode is called below

        $riskColour = @{
            Critical = '#b91c1c'; High = '#c2410c'; Medium = '#a16207'; Low = '#4d7c0f'
        }

        $counts = $rows | Group-Object RiskLevel | ForEach-Object { "$($_.Name): $($_.Count)" }

        $bodyRows = foreach ($r in ($rows | Sort-Object @{e={@{Critical=0;High=1;Medium=2;Low=3}[$_.RiskLevel]}}, SiteTitle)) {
            $c = $riskColour[$r.RiskLevel]
            $expiry = if ($r.Expiration) { [datetime]$r.Expiration | Get-Date -Format 'yyyy-MM-dd' } else { '<em>never</em>' }
            $enc = { param($s) [System.Web.HttpUtility]::HtmlEncode([string]$s) }
            @"
<tr>
  <td><span class="pill" style="background:$c">$($r.RiskLevel)</span></td>
  <td>$(& $enc $r.SiteTitle)</td>
  <td>$(& $enc $r.ItemName)</td>
  <td>$($r.Scope)</td>
  <td>$($r.Access)</td>
  <td>$(& $enc $r.Recipients)</td>
  <td>$expiry</td>
</tr>
"@

        }

        $generated = Get-Date -Format 'yyyy-MM-dd HH:mm'
        # NOT $html - that is the same variable as the [switch]$Html parameter
        # (PowerShell names are case-insensitive), and assigning a string to it
        # throws "Cannot convert String to SwitchParameter".
        $reportHtml = @"
<!doctype html><html><head><meta charset="utf-8">
<title>Sharing Link Audit</title>
<style>
 body{font-family:Segoe UI,system-ui,sans-serif;margin:2rem;color:#1f2430}
 h1{margin:0 0 .25rem}
 .meta{color:#666;margin-bottom:1.5rem}
 .summary span{display:inline-block;margin-right:1rem;font-weight:600}
 table{border-collapse:collapse;width:100%;font-size:.9rem}
 th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #e5e7eb;vertical-align:top}
 th{background:#f3f4f6;position:sticky;top:0}
 .pill{color:#fff;padding:2px 8px;border-radius:10px;font-size:.8rem;font-weight:600}
</style></head><body>
<h1>Sharing Link Audit</h1>
<div class="meta">Generated $generated &middot; $($rows.Count) link(s)</div>
<div class="summary">$([string]::Join(' ', ($counts | ForEach-Object { "<span>$_</span>" })))</div>
<table>
<thead><tr><th>Risk</th><th>Site</th><th>Item</th><th>Scope</th><th>Access</th><th>Shared with</th><th>Expires</th></tr></thead>
<tbody>
$([string]::Join("`n", $bodyRows))
</tbody></table>
</body></html>
"@


        Set-Content -Path $Path -Value $reportHtml -Encoding UTF8
        $file = Get-Item $Path
        Write-Information "Report generated: $($rows.Count) link(s) written to $($file.FullName)" -InformationAction Continue
        return $file
    }
}