Public/Export-SPPermissionMatrix.ps1

function Export-SPPermissionMatrix {
    <#
    .SYNOPSIS
        Exports a scan (from Invoke-SPPermissionScan) as an Excel workbook in the classic
        permission-matrix style.
    .DESCRIPTION
        Per site the workbook contains a "Groups" sheet (columns = principals as assigned
        in SharePoint) and/or a "People" sheet (columns = resolved individuals with their
        highest effective permission). Cells use the same color scheme as the HTML report:
        hue = permission level (FC red, W blue, C violet, R read green), intensity =
        inheritance (pale = inherited, bold/tinted = assigned here). Requires the
        ImportExcel module (Install-Module ImportExcel).
    .PARAMETER InputPath
        Path to the scan JSON file.
    .PARAMETER ScanData
        Scan object (from Invoke-SPPermissionScan -PassThru) or scan JSON FileInfo - pipeline capable.
    .PARAMETER OutputPath
        Path of the .xlsx output file. Default: input path with .xlsx extension.
    .PARAMETER View
        Groups, People or Both (default).
    .PARAMETER IncludeSharingLinks
        Also include sharing-link groups as columns (hidden by default).
    .PARAMETER Open
        Opens the workbook afterwards.
    .EXAMPLE
        Export-SPPermissionMatrix -InputPath .\scan.json -Open
    .EXAMPLE
        Invoke-SPPermissionScan -SiteUrl $url -Depth 2 -ClientId $appId -Interactive | Export-SPPermissionMatrix
    #>

    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Path')]
    [OutputType([System.IO.FileInfo])]
    param(
        [Parameter(Mandatory, ParameterSetName = 'Path', Position = 0)]
        [string]$InputPath,

        [Parameter(Mandatory, ParameterSetName = 'Data', ValueFromPipeline)]
        [psobject]$ScanData,

        [string]$OutputPath,

        [ValidateSet('Groups', 'People', 'Both')]
        [string]$View = 'Both',

        [switch]$IncludeSharingLinks,

        [switch]$Open
    )

    process {
        if (-not (Get-Module -ListAvailable -Name ImportExcel)) {
            throw "The Excel export requires the 'ImportExcel' module. Install it with: Install-Module ImportExcel -Scope CurrentUser"
        }
        Import-Module ImportExcel -ErrorAction Stop

        $source = if ($PSCmdlet.ParameterSetName -eq 'Path') { $InputPath } else { $ScanData }
        $scan = Get-SPMScanObject -InputObject $source

        if (-not $OutputPath) {
            if ($PSCmdlet.ParameterSetName -eq 'Path') {
                $OutputPath = [System.IO.Path]::ChangeExtension((Resolve-Path -Path $InputPath).Path, '.xlsx')
            }
            elseif ($ScanData -is [System.IO.FileInfo]) {
                $OutputPath = [System.IO.Path]::ChangeExtension($ScanData.FullName, '.xlsx')
            }
            else {
                $OutputPath = Join-Path (Get-Location) ('SPPermissionMatrix_{0:yyyyMMdd_HHmmss}.xlsx' -f (Get-Date))
            }
        }

        if (-not $PSCmdlet.ShouldProcess($OutputPath, 'Create Excel workbook')) { return }
        if (Test-Path -Path $OutputPath) { Remove-Item -Path $OutputPath -Force }

        $pkg = Open-ExcelPackage -Path $OutputPath -Create
        try {
            $usedNames = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
            foreach ($site in @($scan.sites)) {
                if ($View -in 'Groups', 'Both') {
                    Add-SPMGroupSheet -Package $pkg -Site $site -UsedNames $usedNames -IncludeSharingLinks:$IncludeSharingLinks
                }
                if ($View -in 'People', 'Both') {
                    Add-SPMPeopleSheet -Package $pkg -Site $site -UsedNames $usedNames -IncludeSharingLinks:$IncludeSharingLinks
                }
            }

            $info = Add-Worksheet -ExcelPackage $pkg -WorksheetName 'Info'
            $info.Cells[1, 1].Value = 'SPPermissionMatrix export'
            $info.Cells[1, 1].Style.Font.Bold = $true
            $info.Cells[2, 1].Value = "Generator: $($scan.generator)"
            $info.Cells[3, 1].Value = "Scan date (UTC): $($scan.scanDate)"
            $info.Cells[4, 1].Value = "Depth: $($scan.depth) - Sites: $(@($scan.sites).Count)"
            $info.Cells[5, 1].Value = "Exported: $(Get-Date -Format 'yyyy-MM-dd HH:mm')"
            $info.Column(1).Width = 70
        }
        finally {
            Close-ExcelPackage $pkg
        }

        Write-Verbose "Workbook saved: $OutputPath"
        if ($Open) { Invoke-Item -Path $OutputPath }
        return (Get-Item -Path $OutputPath)
    }
}