3

I have a PowerShell script that calls out to sqlcmd. After it returns, it seems that it has disabled all the color output in PowerShell (v7.2.0) and I'm getting the ANSI escape sequences output in the window (e.g. [33;1m) I've tried calling [Console]::ResetColor() but that doesn't seem to do anything.

Any ideas how to tell Powershell to start interpreting the color escape sequences instead of printing the extra garbage in the console?

Example the reproduces the problem:

Write-Warning "hello world" 
$process = Start-Process sqlcmd -ArgumentList @('-Q',"`"$($Command)`"") -PassThru -NoNewWindow -Wait
Write-Warning "hello again"

enter image description here

NitrusCS
  • 597
  • 1
  • 5
  • 20
  • 1
    What's plink? Where's the repro script? – derekbaker783 Dec 30 '21 at 14:54
  • 2
    That doesn't look like PowerShell's doing -- ANSI sequences are supported by the new Windows terminal. If that's turned off escape sequence interpretation because `sqlcmd` got started (for some reason), it could be turned on again with a call to `SetConsoleMode` ([see also](https://docs.microsoft.com/windows/console/console-virtual-terminal-sequences)). That would require interop though, I don't know if there's a "simple" PowerShell property to do it. You can also [turn off ANSI entirely](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_ansi_terminals). – Jeroen Mostert Dec 30 '21 at 16:07
  • I have a similar issue with `Invoke-SqlCmd`, for ex `[33;1mVERBOSE: some text ...[0m` – bouvierr Mar 13 '23 at 18:33

2 Answers2

3

Thanks to @Jeroen for the breadcrumbs.

Based on that I have the following solution:

foo.ps1:

.".\Reset-ConsoleColors.ps1"
Write-Warning "hello world" 
$process = Start-Process sqlcmd -ArgumentList @('-Q',"`"$($Command)`"") -PassThru -NoNewWindow -Wait
Reset-ConsoleColors
Write-Warning "hello again"

Reset-ConsoleColors.ps1:

function Reset-ConsoleColors {
    $MethodDefinitions = @'
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint lpMode);
'@
    $Kernel32 = Add-Type -MemberDefinition $MethodDefinitions -Name 'Kernel32' -Namespace 'Win32' -PassThru
    $hConsoleHandle = $Kernel32::GetStdHandle(-11) # STD_OUTPUT_HANDLE 
    $mode = 7
    $Kernel32::SetConsoleMode($hConsoleHandle, $mode) | out-null
}

enter image description here

Still no idea why sqlcmd is changing the console mode or how to tell it not to, but now the console output doesn't go crazy after I call it.

NitrusCS
  • 597
  • 1
  • 5
  • 20
  • That's promising, though note that https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing doesn't just set the VT-enabling flag _in isolation_, but retrieves the existing flags first and combines them with the new flag; also, the _input_ side of things (`STD_INPUT_HANDLE`) may need resetting too. (Not sure how much all of this matters in practice.) – mklement0 Dec 30 '21 at 17:28
  • 1
    I agree: truth and beauty, you should retrieve the value, store it in a variable and then reset it after the call to SQLCMD. My testing shows that you can reuse the console handle (since it's all in a single session) another way to solve this would be to Start-Process in a separate window so that SQLCMD doesn't affect the parent console. – NitrusCS Dec 30 '21 at 19:22
  • Good point, and running in a new window (as `Start-Process` does by default) would indeed bypass the problem. But I'm still curious about _direct, synchronous invocation_ in the _current_ console window (the typical use case when calling console applications): Does the problem still arise if you use `sqlcmd -Q $Command`? – mklement0 Dec 30 '21 at 20:09
  • 1
    Interesting...no, it doesn't. So it's some combination of SQLCMD and Start-Process that causes the issue. (I tried just calling notepad from Start-Process and it does NOT have the problem.) – NitrusCS Dec 30 '21 at 20:59
  • This has been driving me crazy and such an odd error. I was having my color escapes lost when running simple git commands, e.g. 'git branch'. This is a great solution, thanks! – Jordan Ryder Apr 28 '22 at 15:45
0

While the symptom is an interesting one, your feedback tells us that the problem can easily be avoided, and doing so isn't just a workaround, it's actually the superior approach to running console applications synchronously, in the current window:

Instead of Start-Process:

$process = Start-Process sqlcmd -ArgumentList @('-Q',"`"$($Command)`"") -PassThru -NoNewWindow -Wait

use direct invocation:

$output = sqlcmd -Q $Command

Per your feedback, this made the problem go away (interpretation of Virtual Terminal / ANSI escape sequences continued to work).

In addition to running synchronously in the current window, direct invocation:

  • connects the external program's standard output streams to PowerShell's streams, allowing you capture stdout output, as shown above, and to redirect stderr output (with 2>)

  • reflects the external program's (process') exit code in the automatic $LastExitCode variable


Only in unusual cases is Start-Process the right tool for invoking console applications, such as wanting to run in a new window (without -NoNewWindow) - see this answer. GitHub docs issue #6239 provides guidance on when use of Start-Process is and isn't appropriate.

mklement0
  • 382,024
  • 64
  • 607
  • 775