Private/Shared/ConvertTo-SafeJson.ps1

function ConvertTo-SafeJson {
    <#
    .SYNOPSIS
    Converts an object to JSON while masking sensitive properties to prevent them from appearing in logs.
     
    .DESCRIPTION
    This function creates a safe copy of an object and masks sensitive properties before converting to JSON.
    It's designed to prevent sensitive information like tokens, passwords, and secrets from being exposed in logs.
     
    .PARAMETER InputObject
    The object to convert to JSON.
     
    .PARAMETER Depth
    The maximum depth of the JSON conversion.
     
    .PARAMETER SensitiveProperties
    Array of property name patterns that should be masked. Supports wildcard matching.
     
    .EXAMPLE
    ConvertTo-SafeJson $inputConfig -Depth 100
     
    .EXAMPLE
    ConvertTo-SafeJson $parameters -SensitiveProperties @('token', 'password', 'secret')
    #>

    param(
        [Parameter(Mandatory = $true)]
        [AllowNull()]
        [object] $InputObject,
        
        [Parameter(Mandatory = $false)]
        [int] $Depth = 100,
        
        [Parameter(Mandatory = $false)]
        [string[]] $SensitiveProperties = @('github_token', 'githubToken', 'token', 'password', 'secret', 'key', 'credential', 'auth')
    )
    
    if ($null -eq $InputObject) {
        return "null"
    }
    
    # Create a deep copy of the object to avoid modifying the original
    try {
        $jsonString = $InputObject | ConvertTo-Json -Depth $Depth -ErrorAction Stop
        $safeCopy = $jsonString | ConvertFrom-Json -ErrorAction Stop
    } catch {
        # If JSON conversion fails, return a safe string representation
        return "Unable to convert object to JSON: $($_.Exception.Message)"
    }
    
    # Function to recursively mask sensitive properties
    function Hide-SensitiveProperty {
        param([object] $obj)
        
        if ($null -eq $obj) {
            return
        }
        
        if ($obj -is [PSCustomObject]) {
            $obj.PSObject.Properties | ForEach-Object {
                $propertyName = $_.Name
                $propertyValue = $_.Value
                
                # Check if this property name contains sensitive information
                $isSensitive = $false
                foreach ($sensitivePattern in $SensitiveProperties) {
                    if ($propertyName -like "*$sensitivePattern*") {
                        $isSensitive = $true
                        break
                    }
                }
                
                if ($isSensitive -and $null -ne $propertyValue -and $propertyValue -ne "") {
                    # Check if it's a nested object with Value property (like inputConfig structure)
                    if ($propertyValue -is [PSCustomObject] -and $propertyValue.PSObject.Properties['Value']) {
                        if ($null -ne $propertyValue.Value -and $propertyValue.Value -ne "") {
                            $propertyValue.Value = "***MASKED***"
                        }
                    } else {
                        # Direct property assignment
                        $obj.$propertyName = "***MASKED***"
                    }
                } elseif ($propertyValue -is [PSCustomObject] -or $propertyValue -is [array]) {
                    # Recursively process nested objects
                    if ($propertyValue -is [array]) {
                        $propertyValue | ForEach-Object { Hide-SensitiveProperty $_ }
                    } else {
                        Hide-SensitiveProperty $propertyValue
                    }
                }
            }
        } elseif ($obj -is [hashtable]) {
            $keysToMask = @()
            foreach ($key in $obj.Keys) {
                $isSensitive = $false
                foreach ($sensitivePattern in $SensitiveProperties) {
                    if ($key -like "*$sensitivePattern*") {
                        $isSensitive = $true
                        break
                    }
                }
                
                if ($isSensitive) {
                    $keysToMask += $key
                } elseif ($obj[$key] -is [PSCustomObject] -or $obj[$key] -is [hashtable] -or $obj[$key] -is [array]) {
                    Hide-SensitiveProperty $obj[$key]
                }
            }
            $keysToMask | ForEach-Object { 
                if ($null -ne $obj[$_] -and $obj[$_] -ne "") {
                    $obj[$_] = "***MASKED***"
                }
            }
        }
    }
    
    # Mask sensitive properties in the copy
    Hide-SensitiveProperty $safeCopy
    
    # Convert back to JSON
    try {
        return ($safeCopy | ConvertTo-Json -Depth $Depth)
    } catch {
        return "Unable to convert masked object to JSON: $($_.Exception.Message)"
    }
}