Public/Unprotect-SensitiveData.ps1
|
function Unprotect-SensitiveData { <# .SYNOPSIS Reverses Protect-SensitiveData, restoring masked data back to the original values. .DESCRIPTION Unprotect-SensitiveData inverts the replacement map (masked value -> original term) and applies it with the same case-aware engine used by Protect-SensitiveData. This is a best-effort inverse: matching is case-insensitive and the original casing is restored heuristically (an ALL-CAPS masked token comes back ALL-CAPS, etc.), which is correct for typical mask/unmask round-trips but may differ when the original used unusual casing. Like Protect-SensitiveData it works in two modes: - Path mode (-Path): unmasks a single file or every file in a folder. By default the output is written alongside the source using -Suffix; use -InPlace to overwrite. - String mode (-InputString): unmasks a piped string and returns it to the pipeline. If two original terms were masked to the same value the inverse is ambiguous; the function warns and keeps the first mapping. .PARAMETER Path Path to a file or folder to unmask. Belongs to the 'Path' parameter set (default mode). .PARAMETER InputString A masked string to restore. Accepts pipeline input; the result is written to the pipeline. Belongs to the 'String' parameter set. .PARAMETER MappingFile Path to the JSON mapping file used for the original masking. Defaults to MaskSensitiveData_Map.json in the user's profile folder. Ignored when -Replacements is supplied. .PARAMETER Replacements The original (un-inverted) mask hashtable (key = original term, value = masked value). Takes precedence over -MappingFile. The function inverts it internally. .PARAMETER InPlace Path mode only. Overwrite the source files in place instead of writing copies. .PARAMETER Suffix Path mode only. Suffix appended to the output file/folder when not using -InPlace. Defaults to '.unmasked'. .PARAMETER Include Path mode only. Wildcard patterns to filter files when -Path is a folder. Defaults to '*.*'. .PARAMETER Recurse Path mode only. When -Path is a folder, also process files in subfolders. .EXAMPLE Unprotect-SensitiveData -Path .\report.txt.masked Restores the masked file using the default mapping, writing report.txt.masked.unmasked. .EXAMPLE 'Contact alice at Acme' | Unprotect-SensitiveData -Replacements @{ 'john' = 'alice'; 'Contoso' = 'Acme' } Inverts the mask and returns the restored string to the pipeline. .LINK Protect-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 = '.unmasked', [Parameter(ParameterSetName = 'Path')] [string[]]$Include = @('*.*'), [Parameter(ParameterSetName = 'Path')] [switch]$Recurse ) begin { $maskMap = Resolve-ReplacementMap -Replacements $Replacements -MappingFile $MappingFile # Invert the map: masked value -> original term. $map = @{} foreach ($key in $maskMap.Keys) { $maskedValue = $maskMap[$key] if ($map.ContainsKey($maskedValue)) { Write-Warning "Ambiguous inverse: '$maskedValue' maps back to both '$($map[$maskedValue])' and '$key'. Keeping '$($map[$maskedValue])'." continue } $map[$maskedValue] = $key } if ($PSCmdlet.ParameterSetName -eq 'Path') { Write-Host "Loaded $($map.Count) replacement(s) (inverted)." } } process { if ($PSCmdlet.ParameterSetName -eq 'String') { Invoke-Replacements -Text $InputString -Map $map return } Invoke-MaskPath -Path $Path -Map $map -InPlace:$InPlace -Suffix $Suffix ` -Include $Include -Recurse:$Recurse } } |