core/Save-BackupItem.ps1

#Requires -Version 7.0

<#
.SYNOPSIS
Saves a single backup item to a JSON file.

.DESCRIPTION
Converts an item to JSON and saves it to disk. Automatically creates the folder if needed.
Sanitizes the filename by replacing illegal filesystem characters.

.PARAMETER Item
The object to save.

.PARAMETER Folder
The directory where the file will be saved.

.PARAMETER FileName
The base filename to use (without extension). If not provided, uses $Item.displayName.

.PARAMETER PresetFileName
A specific filename to use. Takes precedence over $FileName.

.PARAMETER ScopeTagMap
Optional hashtable of scope tag IDs to display names. When provided, translates roleScopeTagIds values before saving.

.EXAMPLE
Save-BackupItem -Item $policy -Folder "C:\backup" -PresetFileName "MyPolicy"
#>

function Save-BackupItem {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        $Item,

        [Parameter(Mandatory = $true)]
        [string]$Folder,

        [string]$FileName,

        [string]$PresetFileName,

        [hashtable]$ScopeTagMap = @{}
    )

    #region Determine Filename
    $baseFileName = $null

    # Helper: get a property value from either a PSCustomObject or a hashtable/dictionary
    $getProp = { param($obj, $key)
        if ($obj -is [System.Collections.IDictionary]) { $obj[$key] }
        else { $obj.PSObject.Properties[$key]?.Value }
    }

    if ($PresetFileName) {
        $baseFileName = $PresetFileName
    }
    elseif ($FileName) {
        $baseFileName = $FileName
    }
    elseif (& $getProp $Item 'displayName') {
        $baseFileName = & $getProp $Item 'displayName'
    }
    else {
        throw 'Could not determine filename. Provide $PresetFileName, $FileName, or ensure $Item has a displayName property.'
    }
    #endregion Determine Filename

    #region Append ID to filename
    # Append __<id> when the item carries a stable 'id' property and no PresetFileName was given
    if (-not $PresetFileName) {
        $itemId = & $getProp $Item 'id'
        if ($itemId) {
            $baseFileName = "${baseFileName}__${itemId}"
        }
    }
    #endregion Append ID to filename

    # Sanitize Filename
    $baseFileName = ConvertTo-SanitizatedFileName -fileName $baseFileName

    #region Create Folder
    if (!(Test-Path -Path $Folder -PathType Container)) {
        New-Item -ItemType Directory -Path $Folder -Force > $null
        Write-Verbose "Created folder: $Folder"
    }
    #endregion Create Folder

    #region Scope Tag Resolution
    if ($ScopeTagMap.Count -gt 0) {
        $Item = Resolve-ScopeTagNames -InputObject $Item -ScopeTagMap $ScopeTagMap
    }
    #endregion Scope Tag Resolution

    #region Save to JSON
    $filePath = Join-Path -Path $Folder -ChildPath "$baseFileName.json"
    $normalizedItem = ConvertTo-ObjectSortedProperties -InputObject $Item
    $jsonContent = $normalizedItem | ConvertTo-Json -Depth 20
    Set-Content -LiteralPath $filePath -Value $jsonContent -Encoding UTF8 -Force

    Write-Verbose "Saved backup item to: $filePath"
    return $filePath
    #endregion Save to JSON
}

Export-ModuleMember -Function Save-BackupItem