How Do I Redirect Output From Powershell
c#, powershell
Solution
You want to use the `Process.StandardOutput` property. It's a stream that you can open to get the program's output
Problem
I have a similar, but different, question like How to redirect Powershell output from a script run by TaskScheduler and override default width of 80 characters. I have a custom installer framework written long ago. Within it I can execute "tasks". I recently had to add a task to execute a PowerShell script. Now, even though the task is written in C#, I cannot invoke the commands in the PowerShell script directly. That, unfortunately, is off the table. In short, I want to invoke a PowerShell executable from C# and redirect its output back to my application. Here's what I've done so far: I can successfully invoke PowerShell using the following code (from a test project I created): ``` string powerShellExeLocation = null; RegistryKey localKey = Registry.LocalMachine; RegistryKey subKey = localKey.OpenSubKey(@"SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine"); powerShellExeLocation = subKey.GetValue("ApplicationBase").ToString(); if (!Directory.Exists(powerShellExeLocation)) throw new Exception("Cannot locate the PowerShell dir."); powerShellExeLocation = Path.Combine(powerShellExeLocation, "powershell.exe"); if (!File.Exists(powerShellExeLocation)) throw new Exception("Cannot locate the PowerShell executable."); string scriptLocation = Path.Combine(Environment.CurrentDirectory, "PowerShellScript.ps1"); if (!File.Exists(scriptLocation)) throw new Exception("Cannot locate the PowerShell script."); ProcessStartInfo processInfo = new ProcessStartInfo(); processInfo.Verb = "runas"; processInfo.UseShellExecute = false; processInfo.RedirectStandardOutput = true; processInfo.RedirectStandardOutput = true; processInfo.WorkingDirectory = Environment.CurrentDirectory; processInfo.FileName = powerShellExeLocation; processInfo.Arguments = "-NoLogo -OutputFormat Text -NonInteractive -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + scriptLocation + "\" "; Process powerShellProcess = new Process(); powerShellProcess.StartInfo = processInfo; powerShellProcess.OutputDataReceived += new DataReceivedEventHandler(powerShellProcess_OutputDataReceived); powerShellProcess.ErrorDataReceived += new DataReceivedEventHandler(powerShellProcess_ErrorDataReceived); powerShellProcess.Start(); while (!powerShellProcess.HasExited) { Thread.Sleep(500); } ``` My handlers for the redirected output are never called: ``` void powerShellProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e) ``` and ``` void powerShellProcess_OutputDataReceived(object sender, DataReceivedEventArgs e) ``` I am fairly confident that the script is running. Right now I just have a test script that is outputting the PATH contents. ``` $a = $env:path; $a.Split(";") ``` I tested this from within a PowerShell instance and it outputs correctly. What can I do to get the PowerShell output to redirect to my C# code? I don't want to rely on the scripts that I will execute to handle their own logging. I'd rather gather their output and handle it within the framework's logging mechanism. EDIT Realized I left the odd loop thing to wait for exit in that code example. I've had the process "WaitForExit": ``` powerShellProcess.WaitForExit(); ``` EDIT Using the suggestion from @MiniBill. I came up with the following code snippit: ``` powerShellProcess.Start(); while (!powerShellProcess.HasExited) { string line; while (!string.IsNullOrEmpty((line = powerShellProcess.StandardOutput.ReadLine()))) this.LogInfo("PowerShell running: " + line); } ``` This handles the output fine. It's cutting my lines at 80 characters :(, but I can live with that unless someone else can provide a suggestion to fix that! UPDATE A Solution ``` /* * The next few lines define the process start info for the PowerShell executable. */ ProcessStartInfo processInfo = new ProcessStartInfo(); processInfo.Verb = "runas"; processInfo.UseShellExecute = false; processInfo.RedirectStandardError = true; processInfo.RedirectStandardOutput = true; processInfo.WorkingDirectory = Environment.CurrentDirectory; //this FileName was retrieved earlier by looking in the registry for key "HKLM\SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine" and the value for "ApplicationBase" processInfo.FileName = powerShellExeLocation; //if we're going to use script arguments build up the arguments from the process start correctly. if (!string.IsNullOrEmpty(this.ScriptArguments)) processInfo.Arguments = "-NoLogo -NonInteractive -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + ScriptLocation + "\" '" + ScriptArguments + "'"; else processInfo.Arguments = "-NoLogo -NonInteractive -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + ScriptLocation + "\""; //create the Process object, set the start info, and start the process Process powerShellProcess = new Process(); powerShellProcess.StartInfo = processInfo; powerShellProcess.Start(); /* * While the PowerShell process hasn't exited do the following: * * Read from the current position of the StandardOutput to the end by each line and * update the tasks progress with this information * * Read from the current position of StandardError to the end by each line, update * the task's progress with this information and set that we have an error. * * Sleep for 250 milliseconds (1/4 of a sec) */ bool isError = false; while (!powerShellProcess.HasExited) { string standardOuputLine, standardErrorLine; while (!string.IsNullOrEmpty((standardOuputLine = powerShellProcess.StandardOutput.ReadLine()))) { this.UpdateTaskProgress(standardOuputLine); } while (!string.IsNullOrEmpty((standardErrorLine = powerShellProcess.StandardError.ReadLine()))) { this.UpdateTaskProgress(standardErrorLine); isError = true; } Thread.Sleep(250); } /* * Now that the process has completed read to the end of StandardOutput and StandardError. * Update the task progress with this information. */ string finalStdOuputLine = powerShellProcess.StandardOutput.ReadToEnd(); string finalStdErrorLine = powerShellProcess.StandardError.ReadToEnd(); if (!string.IsNullOrEmpty(finalStdOuputLine)) this.UpdateTaskProgress(finalStdOuputLine); if (!string.IsNullOrEmpty(finalStdErrorLine)) { this.UpdateTaskProgress(finalStdErrorLine); isError = true; } // there was an error during the run of PowerShell that was output to StandardError. This doesn't necessarily mean that // the script error'd but there was a problem. Throw an exception for this. if (isError) throw new Exception(string.Format(CultureInfo.InvariantCulture, "Error during the execution of {0}.", this.ScriptLocation)); ```