Private/Invoke-MaskPath.ps1

function Invoke-MaskPath {
    <#
    .SYNOPSIS
        Applies a replacement map to a file or every matching file in a folder.
    .DESCRIPTION
        Internal worker shared by Protect-SensitiveData and Unprotect-SensitiveData. Handles single
        files and folders, in-place vs. copy-with-suffix output, recursion, include filters, and
        masking of file/relative names. The caller supplies the already-resolved replacement map.
    #>

    param(
        [string]$Path,
        [hashtable]$Map,
        [switch]$InPlace,
        [string]$Suffix,
        [string[]]$Include,
        [switch]$Recurse
    )

    $item = Get-Item $Path -ErrorAction Stop

    if ($item.PSIsContainer) {
        $getParams = @{ Path = $item.FullName; File = $true }
        if ($Recurse) { $getParams['Recurse'] = $true }

        $files = Get-ChildItem @getParams | Where-Object {
            $name = $_.Name
            $Include | Where-Object { $name -like $_ }
        }

        Write-Host "Found $($files.Count) file(s) in folder '$($item.FullName)'."

        if (-not $InPlace) {
            $outRoot = Join-Path $item.Parent.FullName ($item.Name + $Suffix)
            New-Item -ItemType Directory -Path $outRoot -Force | Out-Null
            Write-Host "Output folder: $outRoot"
        }
    } else {
        $files   = @($item)
        $outRoot = $null
        Write-Host "Processing single file: $($item.FullName)"
    }

    foreach ($file in $files) {
        $content         = [System.IO.File]::ReadAllText($file.FullName)
        $original_length = $content.Length

        # Transform content and file name
        $newContent = Invoke-Replacements -Text $content -Map $Map
        $newName    = Invoke-Replacements -Text $file.Name -Map $Map

        # Determine output path
        if ($InPlace) {
            $outDir  = $file.DirectoryName
            $outPath = Join-Path $outDir $newName

            if ($newName -ne $file.Name) {
                Rename-Item -Path $file.FullName -NewName $newName
            }
        } elseif ($item.PSIsContainer) {
            $relative    = $file.FullName.Substring($item.FullName.Length).TrimStart('\', '/')
            $relativeNew = Invoke-Replacements -Text $relative -Map $Map
            $outPath     = Join-Path $outRoot $relativeNew
            $outDir      = Split-Path $outPath
            if (-not (Test-Path $outDir)) {
                New-Item -ItemType Directory -Path $outDir -Force | Out-Null
            }
        } else {
            $outPath = Join-Path $file.DirectoryName "$newName$Suffix"
        }

        [System.IO.File]::WriteAllText($outPath, $newContent)

        Write-Host "✔ $($file.Name) → $outPath ($original_length chars read, $($newContent.Length) chars written)"
    }
}