0

TLDW: To support a specific process I need to go from a powershell instances/module to kick off a known safe executable for some c# magic, which then in turn needs to kick and execute a powershell script before exiting.

Powershell Entry point { Known Good Exe { Powershell Work To Do } }

Now ideally, this would all run from a single console instance so that all of the output is simple to look at. The exe -> powershell logging all works fine and as expected when using powershell.Streams.... The write-hosts in the powershell work all show up in the console and I get all the info I want.

powerShell.Streams.Information.DataAdded += LogMessage;

The problem comes when the outer powershell module is introduced. This one is needed because the parent process and execution environment this is running from is powershell. Once this whole stack is started from within a powershell instance, I get console logging from the outer powershell, and from the exe. BUT all of the write-hosts from the inner powershell modules disappear.

I've tried disabling the stream redirects, and a few other things, but this isn't resolving in the manner I would hope. I'm hoping someone knows if there is a way to get this to work as it solves so many problems if it just would.

PowerShell Outer:

$C#Exe = Join-Path $PSScriptRoot $C#ExePath
$C#Args = @()


Write-Host "Hello world" # will show up

& $C#Exe  $C#Args

C# Exe Code:

public static void Main(string[] args)
{
    Console.WriteLine("Hello world"); #Will show up

    var powerShell = PowerShell.Create().AddScript("PowerShellInner.ps1");

    powerShell.Streams.Information.DataAdded += LogMessage<InformationRecord>;
    powerShell.Streams.Warning.DataAdded += LogMessage<WarningRecord>;
    powerShell.Streams.Error.DataAdded += LogMessage<ErrorRecord>;
    powerShell.Streams.Verbose.DataAdded += LogMessage<VerboseRecord>;
    powerShell.Streams.Debug.DataAdded += LogMessage<DebugRecord>;


    StringBuilder resultString = new StringBuilder();

    foreach (dynamic item in powerShell.Invoke().ToList())
    {
        resultString.AppendLine(item.ToString());
    }

    Console.WriteLine(resultString.ToString());

}

private static void LogMessage<T>(object sender, DataAddedEventArgs e)
{
    var data = (sender as PSDataCollection<T>)[e.Index];
    Console.WriteLine($"[{typeof(T).Name}] {Convert.ToString(data)}");
}

PowerShell Inner:

Write-Host "Hello world" #Wont show up
  • Does this answer your question? [Stderr and stdout for powershell with Invoke-Expression has no output or errors](https://stackoverflow.com/questions/43784702/stderr-and-stdout-for-powershell-with-invoke-expression-has-no-output-or-errors) – VA systems engineer Jan 20 '20 at 18:12
  • No, and maybe to clarify, my real question is why does running this with the outer powershell cause the c# ps stream redirection to fail? – Ethan Criss Jan 20 '20 at 18:17
  • So, you have a powershell script invoking an exe (written in C#) and the C# program is, in turn, executing a powershell script? I'm getting confused because when you say "Powershell", I'm not sure if you mean the PS that's executing the exe, or if you mean the PS that the exe is executing – VA systems engineer Jan 20 '20 at 18:42
  • @Nova you are precisely correct in your summary of the call stack. – Ethan Criss Jan 20 '20 at 18:44
  • So, the PS you're showing in your question is the "outer" PS, yes? – VA systems engineer Jan 20 '20 at 18:45
  • Maybe you need to post all your code. I don't get the presence of the `powershell.Invoke` statement that apparently is in your 'outer' script while you then ask why the stream from within the 'Known Good Exe" (whose code I presume we can't see) is disappearing – VA systems engineer Jan 20 '20 at 18:50
  • Ok hopefully that code clarifies things. If I just call the .exe and let it run, everything shows up. If I run the PowerShellOuter.ps1, anything in PowerShellInner fails to show in the console. – Ethan Criss Jan 20 '20 at 19:06
  • See my updated answer for working code – VA systems engineer Jan 22 '20 at 13:31

1 Answers1

1

UPDATE on 1/22/2020

I can't fully explain why you're experiencing what you're seeing, but the following code works.

Output from executing PowerShellOuter.ps1

enter image description here

Code

Notes:

  • Your c# program doesn't show any code manipulating input arguments,
    so I didn't model any input args

  • You mis-used the AddScript method. It needs the text of the script, not the script name

  • The code below assumes that the c# exe and the two PS scripts are in the same folder

  • In PowerShellInner.ps1, use write-output. write-host does not output data to PowerShell Objectflow Engine but rather, as the name implies, writes directly to the host and sends nothing to the PowerShell engine to be forwarded to commands later in the pipeline. See Write-Output or Write-Host in PowerShell

PowerShellOuter.ps1

cls
$CSharpExe = Join-Path $PSScriptRoot "PowerShellExecutionSample.exe"
Write-Host "Hello from PowerShellOuter.ps1"
&$CSharpExe
pause

PowerShellInner.ps1

Write-Output "Hello from PowerShellInner.ps1"

Code for PowerShellExecutionSample.exe

using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Management.Automation;

namespace PowerShellExecutionSample
{
    class Program
    {
        static void Main(string[] args)
        {
            PowerShellExecutor t = new PowerShellExecutor();
            t.ExecuteSynchronously();
        }
    }

    /// <summary>
    /// Provides PowerShell script execution examples
    /// </summary>
    class PowerShellExecutor
    {
        public void ExecuteSynchronously()
        {
            using (PowerShell PowerShellInstance = PowerShell.Create())
            {
                //You mis-used the AddScript method. It needs the text of the script, not the script name
                string scriptText = File.ReadAllText(string.Format(@"{0}\PowerShellInner.ps1", Environment.CurrentDirectory));

                PowerShellInstance.AddScript(scriptText);
                Collection <PSObject> PSOutput = PowerShellInstance.Invoke();

                // loop through each output object item
                foreach (PSObject outputItem in PSOutput)
                {
                    // if null object was dumped to the pipeline during the script then a null
                    // object may be present here. check for null to prevent potential NRE.
                    if (outputItem != null)
                    {
                        Console.WriteLine(outputItem.BaseObject.ToString() + "\n");
                    }
                }
            }
        }
    }
}

Original Answer on 1/21/2020

Updating your question to break out your code out helped - thanks.

I think you have two issues:

1) Modify your c# program to obtain the streams coming from PowerShell Inner and make your c# program re-emit the data from the PowerShell Inner output streams. Here is a Microsoft blog entry I used to crack the same nut: Executing PowerShell scripts from C#

2) Modify your PowerShell Outer to obtain the streams coming from the c# program. Here is a blog entry that seems to crack that nut: How to redirect output of console program to a file in PowerShell. The heart of this is to execute the following from your PowerShell Outer:

cmd /c XXX.exe ^>log.txt 2^>^&1

Note: The ^ are really backticks

VA systems engineer
  • 2,856
  • 2
  • 14
  • 38
  • So I'm already using the powershell streams to get the output, but I went ahead and implemented the PSDataCollection from the c# code from your first link with no change to behavior. I then tried using the second suggestion, but that also didnt change anything. Additionally it would be really great if all of this logging was in realtime instead of needing to be written out to a txt before it can be view. To me this really seems like a problem with having 2 powershell instances both running at the same time. Could this be two different psexe that aren't able to communicate? – Ethan Criss Jan 20 '20 at 20:21
  • @EthanCriss: See my updated answer for working code – VA systems engineer Jan 22 '20 at 17:42
  • This looks to have been the rc. //You mis-used the AddScript method. It needs the text of the script, not the script name – Ethan Criss Jan 22 '20 at 20:04
  • @EthanCriss Things work when your PowerShell inner script uses write-host instead of write-output? – VA systems engineer Jan 23 '20 at 17:35