Private/Helper/ConvertTo-CWMRedacted.ps1
|
function ConvertTo-CWMRedacted { # Returns a copy of $InputObject that is safe to print in verbose, debug and -WhatIf output. # - Values whose key (or property name) matches password|token|privateKey|secret|memberHash (any case) # become '[redacted]', in nested hashtables, PSCustomObjects and arrays too. # - A JSON Patch operation whose 'path' matches has its 'value' redacted. # - A JSON string is parsed, redacted and serialized again; it is returned unchanged when nothing matches. # - A byte[] (e.g. a multipart upload) becomes '[N bytes]'. # Everything else is returned as is. The input is never modified. [CmdletBinding()] param( [Parameter(Position = 0)] [AllowNull()] $InputObject, [int]$Depth = 0 ) $Pattern = 'password|token|privateKey|secret|memberHash' $Mask = '[redacted]' if ($null -eq $InputObject) { return $null } if ($InputObject -is [byte[]]) { return "[$($InputObject.Length) bytes]" } if ($Depth -gt 50) { return $InputObject } if ($InputObject -is [string]) { # Nothing that could be a sensitive key: keep the text exactly as it is if ($InputObject -notmatch $Pattern) { return $InputObject } $Trimmed = $InputObject.Trim() if ($Trimmed.StartsWith('{') -or $Trimmed.StartsWith('[')) { try { $Parsed = ConvertFrom-Json -InputObject $InputObject -ErrorAction Stop } catch { return $Mask } return ConvertTo-Json -InputObject (ConvertTo-CWMRedacted $Parsed -Depth ($Depth + 1)) -Depth 100 } # Form-encoded text, e.g. UserName=u&Password=p return $InputObject -replace "(?i)([^=&\s]*(?:$Pattern)[^=&\s]*=)[^&\s]*", "`$1$Mask" } $IsDictionary = $InputObject -is [System.Collections.IDictionary] if ($IsDictionary -or $InputObject -is [System.Management.Automation.PSCustomObject]) { $Copy = [ordered]@{} if ($IsDictionary) { foreach ($Key in $InputObject.Keys) { $Copy[[string]$Key] = $InputObject[$Key] } } else { foreach ($Property in $InputObject.psobject.Properties) { $Copy[$Property.Name] = $Property.Value } } foreach ($Key in @($Copy.Keys)) { if ($Key -match $Pattern) { $Copy[$Key] = $Mask } else { $Copy[$Key] = ConvertTo-CWMRedacted $Copy[$Key] -Depth ($Depth + 1) } } # JSON Patch: @{ op = 'replace'; path = 'password'; value = '...' } if ($Copy.Contains('op') -and $Copy.Contains('value') -and "$($Copy['path'])" -match $Pattern) { $Copy['value'] = $Mask } if ($IsDictionary) { return $Copy } return [pscustomobject]$Copy } if ($InputObject -is [System.Collections.IEnumerable]) { $Items = New-Object System.Collections.Generic.List[object] foreach ($Item in $InputObject) { $Items.Add((ConvertTo-CWMRedacted $Item -Depth ($Depth + 1))) } # The unary comma keeps an array (also with one or no element) from being unrolled return , $Items.ToArray() } $InputObject } |