Private/Save-SPCPermissionSnapshot.ps1

function Save-SPCPermissionSnapshot {
    <#
    .SYNOPSIS
        Saves a JSON snapshot of a user's permissions before removal per SRS 6.2 (Schema v1.1).
    .DESCRIPTION
        Serializes user permissions and group memberships to JSON snapshot format v1.1.
        Supports empty permission sets via isEmptyPermissionSet boolean flag and empty array [].
    .OUTPUTS
        [System.IO.FileInfo]
    #>

    [CmdletBinding()]
    [OutputType([System.IO.FileInfo])]
    param(
        [Parameter(Mandatory)]
        [string] $UserLoginName,

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

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

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

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

        # Array of { scope: string, permissionLevel: string, inheritanceStatus: string }
        [Parameter(Mandatory)]
        [AllowEmptyCollection()]
        [object[]] $Permissions,

        # Array of { groupId: int, groupName: string }
        [Parameter(Mandatory)]
        [AllowEmptyCollection()]
        [object[]] $GroupMemberships,

        [Parameter(Mandatory)]
        [string] $SnapshotPath
    )

    process {
        # Use List[object] so ConvertTo-Json serializes as [] not null (PS E011 pattern).
        $permList  = [System.Collections.Generic.List[object]]::new()
        if ($Permissions)      { foreach ($p in $Permissions)      { $permList.Add($p) } }
        $groupList = [System.Collections.Generic.List[object]]::new()
        if ($GroupMemberships) { foreach ($g in $GroupMemberships) { $groupList.Add($g) } }

        $isEmpty = ($permList.Count -eq 0)

        # SRS 6.2 schema v1.1
        $snapshot = [ordered]@{
            '$schema'            = 'https://m365automation.com/schemas/spclean/snapshot-v1.1.json'
            snapshotVersion      = '1.1'
            createdAt            = (Get-Date).ToUniversalTime().ToString('o')
            tenantName           = $TenantName
            siteUrl              = $SiteUrl
            user                 = [ordered]@{
                loginName   = $UserLoginName
                displayName = $UserDisplayName
                upn         = $UserUPN
            }
            isEmptyPermissionSet = $isEmpty
            permissions          = $permList
            groupMemberships     = $groupList
        }

        if (-not (Test-Path -Path $SnapshotPath)) {
            New-Item -ItemType Directory -Path $SnapshotPath -Force | Out-Null
        }

        $safeUpn  = $UserUPN -replace '[^a-zA-Z0-9@._-]', '_'
        $stamp    = (Get-Date).ToUniversalTime().ToString('yyyyMMddHHmmss') + 'Z'
        $filePath = Join-Path -Path $SnapshotPath -ChildPath "${safeUpn}_${stamp}.json"

        $snapshot | ConvertTo-Json -Depth 10 | Set-Content -Path $filePath -Encoding UTF8

        Write-Verbose "Snapshot saved: $filePath"
        Get-Item -Path $filePath
    }
}