diff --git a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs index e87786c7ca5..f166f8aa02d 100644 --- a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs +++ b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs @@ -1520,11 +1520,7 @@ private static bool WriteScriptBlockToLog(ScriptBlock scriptBlock, int segment, if (!wasEncoded) { - // The logging mechanism(s) cannot handle null and rendering may not be able to handle - // null as we have the string defined as a null terminated string in the manifest. - // So, replace null characters with the Unicode `SYMBOL FOR NULL` - // We don't just remove the characters to preserve the fact that a null character was there. - textToLog = textToLog.Replace('\u0000', '\u2400'); + textToLog = FormatLogString(textToLog); } if (scriptBlock._scriptBlockData.HasSuspiciousContent) @@ -1557,6 +1553,50 @@ private static bool WriteScriptBlockToLog(ScriptBlock scriptBlock, int segment, return true; } + private static string FormatLogString(string textToLog) + { + const char NullControlChar = '\u0000'; + + // The null symbol - `␀` + const char NullSymbolChar = '\u2400'; + + // No logging mechanism(s) cannot handle null and rendering may not be able to handle + // null as we have the string defined as a null terminated string in the manifest. + // So, replace null characters with the Unicode `SYMBOL FOR NULL` + // We don't just remove the characters to preserve the fact that a null character was there. +#if UNIX + const char LinefeedControlChar = '\u000A'; + const char CarriageReturnControlChar = '\u000D'; + + // We chose the return symbol because we believe it is more associated with these concepts + // than the carriage return (␍), line feed (␊), or new line (␤) symbols. + // The return symbol - `⏎` + const char ReturnSymbolChar = '\u23CE'; + + if (Platform.IsLinux) + { + // Because the creation of the string builder is expensive + // We only do this on Linux where we are doing multiple replace operations + StringBuilder logBuilder = new StringBuilder(textToLog); + + logBuilder.Replace(NullControlChar, NullSymbolChar); + + // Syslog (only used on Linux) encodes CR and NL to their octal values. + // We will replace them with a unicode 'RETURN SYMBOL' (U+23CE) charater for easier viewing + logBuilder.Replace(LinefeedControlChar, ReturnSymbolChar); + logBuilder.Replace(CarriageReturnControlChar, ReturnSymbolChar); + + return logBuilder.ToString(); + } + else + { + return textToLog.Replace(NullControlChar, NullSymbolChar); + } +#else + return textToLog.Replace(NullControlChar, NullSymbolChar); +#endif + } + private static bool GetAndValidateEncryptionRecipients( ScriptBlock scriptBlock, ProtectedEventLogging logSetting) diff --git a/test/powershell/Host/Logging.Tests.ps1 b/test/powershell/Host/Logging.Tests.ps1 index 0a2566de030..2a9dcbe3b91 100644 --- a/test/powershell/Host/Logging.Tests.ps1 +++ b/test/powershell/Host/Logging.Tests.ps1 @@ -156,9 +156,9 @@ Describe 'Basic SysLog tests on Linux' -Tag @('CI','RequireSudoOnUnix') { $IsSupportedEnvironment = $false } [string] $powershell = Join-Path -Path $PSHome -ChildPath 'pwsh' - $scriptBlockCreatedRegExTemplate = @' -Creating Scriptblock text \(1 of 1\):#012{0}(#012)*ScriptBlock ID: [0-9a-z\-]*#012Path:.* -'@ + $scriptBlockCreatedRegExTemplate = @" +Creating Scriptblock text \(1 of 1\):#012{0}(⏎|#012)*ScriptBlock ID: [0-9a-z\-]*#012Path:.* +"@ } } @@ -211,7 +211,7 @@ $pid $createdEvents[0].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f ".*/$testFileName") # Verify we log that we are the script to create the scriptblock - $createdEvents[1].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f (Get-RegEx -SimpleMatch $Script.Replace([System.Environment]::NewLine,'#012'))) + $createdEvents[1].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f (Get-RegEx -SimpleMatch $Script.Replace([System.Environment]::NewLine,"⏎"))) # Verify we log that we are excuting the created scriptblock $createdEvents[2].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f "Write\-Verbose 'testheader123' ;Write\-verbose 'after'") @@ -240,7 +240,7 @@ $pid $createdEvents[0].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f ".*/$testFileName") # Verify we log that we are the script to create the scriptblock - $createdEvents[1].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f (Get-RegEx -SimpleMatch $Script.Replace([System.Environment]::NewLine,'#012'))) + $createdEvents[1].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f (Get-RegEx -SimpleMatch $Script.Replace([System.Environment]::NewLine,"⏎"))) # Verify we log that we are excuting the created scriptblock $createdEvents[2].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f "Write\-Verbose 'testheader123␀' ;Write\-verbose 'after'")