0

I have a C# assembly that processes an xml file and at the end spits out the results to the console.
e.g. Console.WriteLine(_header.ToString());
I can load this dll in powershell and call the right method like this:

[sqlproj_doctor.sqlprojDoctor]::ProcessXML($file) | out-file ./test.xml

All is well.

The problem begins when I want to redirect the output. For some reason stdout is empty. What am I missing? I need to be further process the output of this dll.

Note: If I compile the same code as an executable, it correctly populates the standard output stream and I can redirect the output.

another note: as a workaround, I changed the method from void to string, and can now manipulate the returned string.

Mordechai
  • 718
  • 1
  • 8
  • 23
  • does it really use `Console.WriteLine` and not something else to display text on console? – user4003407 Oct 13 '15 at 19:58
  • I pasted the exact line of code that is used in the class library ... – Mordechai Oct 13 '15 at 20:03
  • [extend a `TextWriter` and override `WriteLine(string)`, then assign to Console](http://stackoverflow.com/questions/6024172/is-it-possible-to-intercept-console-output) – Mathias R. Jessen Oct 13 '15 at 20:08
  • thanks Mathias, but the article you reference describes how to intercept an stdout stream. in my case, stdout is empty. That's the problem. Also, let's be mindful of contexts. In your example, everything is happening within one app. In my case a dll is being loaded and a method is being called from a powershell console. – Mordechai Oct 13 '15 at 20:20
  • @MorDeror Can you show, how did you try to redirect output? If `ProcessXML` only use `Console.WriteLine`, than method proposed by Mathias R. Jessen should work for you. And if you own assembly code, as follows from your note *I changed the method*, why not to rewrite method to accept `TextWriter` to write to instead of use `Console.WriteLine`? – user4003407 Oct 13 '15 at 21:19
  • Hi @PetSerAl, the asssembly method is invoked from powershell console as shown in the question. About your suggestion, I guess I don't follow what would TextWriter change here. After all it's an abstract class and needs to be implemented by a streamwriter which brings us to square one. Also, note that the question is not about finding a workaround, rather understanding why Console.Writeline output doesn't wind up in stdout. – Mordechai Oct 14 '15 at 11:26
  • @MorDeror `Console.WriteLine` is just a shortcut to `Console.Out.WriteLine`, and someone can replace `Console.Out` by calling `Console.SetOut`, so anything written to it does not go to stdout, but go to somewhere else, and it is how you can capture something written with `Console.WriteLine`. And you still not show, how did you try to redirect stdout. – user4003407 Oct 14 '15 at 11:55
  • @PetSerAl, I thought it was obvious how the redirect would work, but I updated the example just in case. regarding Console.Writeline, I understand what you're explaining, but I'm not doing anything to intercept the console output... – Mordechai Oct 14 '15 at 13:19

1 Answers1

3

When you call [Console]::WriteLine('SomeText'), it write to PowerShell process stdout, not to command output, and so it can not be redirected from inside same PowerShell process, by standard PowerShell operators, like this:

[Console]::WriteLine('SomeText')|Out-File Test.txt

You have to spawn new PowerShell process, and redirect output of that new process:

powershell -Command "[Console]::WriteLine('SomeText')"|Out-File Test.txt

In case if some command use [Console]::WriteLine to write to console, you can capture that output without starting new PowerShell instance:

$OldConsoleOut=[Console]::Out
$StringWriter=New-Object IO.StringWriter
[Console]::SetOut($StringWriter)

[Console]::WriteLine('SomeText') # That command will not print on console.

[Console]::SetOut($OldConsoleOut)
$Results=$StringWriter.ToString()
$Results # That variable would contain "SomeText" text.

Although this does not help if command does not use [Console]::WriteLine, but write to stdout stream directly:

$OldConsoleOut=[Console]::Out
$StringWriter=New-Object IO.StringWriter
[Console]::SetOut($StringWriter)

$Stdout=[Console]::OpenStandardOutput()
$Bytes=[Console]::OutputEncoding.GetBytes("SomeText"+[Environment]::NewLine)
$Stdout.Write($Bytes,0,$Bytes.Length) # That command will print on console.
$Stdout.Close()

[Console]::SetOut($OldConsoleOut)
$Results=$StringWriter.ToString()
$Results # That variable will be empty.
user4003407
  • 21,204
  • 4
  • 50
  • 60
  • interesting. Thank you for a thorough explanation with examples. I was able to capture the output to a stringwriter. So, is the problem here in that stdout of my program and stdin of my redirect pipe are in a different context? And that's why redirecting all stdout to a stringwriter works, because all output is dumped there regardless of what execution context it occurred in? – Mordechai Oct 14 '15 at 18:26
  • @MorDeror When you call to external dll, it does not create external process with their own stdout to capture. It work inside PowerShell process. From PowerShell point of view, .NET methods does not have stdout (PowerShell have its own object based pipeline, and it does not try to recognize any write to console), them only have return values. `[Console]::SetOut` trick only work inside process, and only if you somehow use `[Console]::Out`. For example, PowerShell itself write to console true WinAPI, and so not affected by `[Console]::SetOut`. – user4003407 Oct 14 '15 at 19:21