Public/Protect-SensitiveData.ps1

function Protect-SensitiveData {
    <#
    .SYNOPSIS
        Masks sensitive data in files, folders, or strings by applying case-aware text replacements.

    .DESCRIPTION
        Protect-SensitiveData replaces occurrences of configured terms with their masked
        counterparts. Replacements are case-insensitive when matching but case-aware when
        substituting: an ALL-CAPS, all-lower, or PascalCase match is replaced with a value
        adapted to the same casing style.

        The function operates in one of two modes, selected automatically by the parameter used:

        - Path mode (-Path): masks the contents (and names) of a single file or every file in
          a folder. By default the masked output is written alongside the source using -Suffix;
          use -InPlace to overwrite the originals.

        - String mode (-InputString): masks a raw string and returns the masked string to the
          pipeline. No files are read or written, so this mode is suitable for inline use and
          piping. Status messages are suppressed so downstream output stays clean.

        The replacement map is supplied either via -Replacements (a hashtable) or -MappingFile
        (a JSON file). By default the mapping file is read from the current user's profile
        directory (MaskSensitiveData_Map.json). Manage the mapping file with Add-MaskMapping,
        Remove-MaskMapping, and Get-MaskMapping. Reverse the operation with Unprotect-SensitiveData.

    .PARAMETER Path
        Path to a file or folder to mask. When a folder is given, files matching -Include are
        processed (optionally recursively with -Recurse). Belongs to the 'Path' parameter set
        and is the default mode.

    .PARAMETER InputString
        A string to mask. Accepts pipeline input. The masked string is written to the pipeline
        and no file I/O is performed. Belongs to the 'String' parameter set.

    .PARAMETER MappingFile
        Path to a JSON file containing the replacement map (key = term to find, value =
        replacement). Defaults to MaskSensitiveData_Map.json in the user's profile folder.
        Ignored when -Replacements is supplied.

    .PARAMETER Replacements
        A hashtable of replacements (key = term to find, value = replacement). When supplied,
        this takes precedence over -MappingFile.

    .PARAMETER InPlace
        Path mode only. Overwrite the source files in place instead of writing masked copies.

    .PARAMETER Suffix
        Path mode only. Suffix appended to the masked output file/folder when not using
        -InPlace. Defaults to '.masked'.

    .PARAMETER Include
        Path mode only. One or more wildcard patterns used to filter files when -Path is a
        folder. Defaults to '*.*' (all files).

    .PARAMETER Recurse
        Path mode only. When -Path is a folder, also process files in subfolders.

    .EXAMPLE
        Protect-SensitiveData -Path .\report.txt

        Masks report.txt using the default mapping file and writes report.txt.masked alongside it.

    .EXAMPLE
        Protect-SensitiveData -Path .\logs -Recurse -InPlace

        Masks every file under the logs folder (recursively), overwriting the originals.

    .EXAMPLE
        'Contact john.doe@contoso.com' | Protect-SensitiveData

        Masks the piped string and returns the masked text to the pipeline.

    .EXAMPLE
        Get-Content .\input.txt | Protect-SensitiveData -Replacements @{ 'Contoso' = 'Acme' }

        Pipes each line through the masker using an inline replacement hashtable.

    .LINK
        Unprotect-SensitiveData
    #>

    [CmdletBinding(DefaultParameterSetName = 'Path')]
    param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'Path')]
        [string]$Path,

        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'String')]
        [string]$InputString,

        [string]$MappingFile = (Join-Path $env:USERPROFILE 'MaskSensitiveData_Map.json'),
        [hashtable]$Replacements,

        [Parameter(ParameterSetName = 'Path')]
        [switch]$InPlace,

        [Parameter(ParameterSetName = 'Path')]
        [string]$Suffix = '.masked',

        [Parameter(ParameterSetName = 'Path')]
        [string[]]$Include = @('*.*'),

        [Parameter(ParameterSetName = 'Path')]
        [switch]$Recurse
    )

    begin {
        $map = Resolve-ReplacementMap -Replacements $Replacements -MappingFile $MappingFile

        if ($PSCmdlet.ParameterSetName -eq 'Path') {
            Write-Host "Loaded $($map.Count) replacement(s)."
        }
    }

    process {
        if ($PSCmdlet.ParameterSetName -eq 'String') {
            # String mode: mask the text and emit it to the pipeline (no file I/O).
            Invoke-Replacements -Text $InputString -Map $map
            return
        }

        Invoke-MaskPath -Path $Path -Map $map -InPlace:$InPlace -Suffix $Suffix `
            -Include $Include -Recurse:$Recurse
    }
}