- Регистрация
- 9 Май 2015
- Сообщения
- 1,483
- Баллы
- 155

Ever needed your C# application to run a PowerShell script, wait until it's done, and print the output directly in your console?
Maybe you're automating a deployment process, launching a diagnostic script, or integrating with a CLI tool like Git, Docker, or ffmpeg.
After a few years, in these days, I had to do it again and I think I found the right way again, because I forgot how to do it!

Let’s dive in.

Running external commands from a C# application is more common than you might think. In many real-world scenarios, you don’t want to reimplement logic that already exists in command-line tools or scripts.
Here are a few examples where this is useful:
Automation & DevOps: Running PowerShell scripts for provisioning resources, deploying software, or managing files.
Tool Integration: Calling docker, git, az, or ffmpeg to leverage existing CLI workflows inside your app.
Legacy Compatibility: Executing older batch or shell scripts without rewriting them in C#.
Hybrid Workflows: Combining the power of .NET with external tools to create flexible automation pipelines.
Tip: While it's easy to start a process, doing it right — especially handling output and waiting for completion — requires some care. That’s exactly what I will cover next.

To follow along, you’ll need a basic .NET console application. Here's how to get started:

- (I use .NET 9 but from .NET 6 is more or less the same)
- PowerShell (included on Windows by default)
- An IDE like Visual Studio, JetBrains Rider, or just your favorite code editor and terminal

In your terminal:
dotnet new console -n ConsoleWaitScript
cd ConsoleWaitScript
This scaffolds a simple C# console app with a Program.cs file.
Next, replace the contents of Program.cs with the following code.
I'll break it down in detail in the next section.

Let’s dive into the heart of the project — running an inline PowerShell script from C# and waiting for it to complete.
Here’s the full code:
using System;
using System.Diagnostics;
namespace ConsoleWaitScript
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("PowerShell Script Launcher");
for (int i = 0; i < 10; i++)
{
RunInlineCountdownScript(10);
}
}
static void RunInlineCountdownScript(int seconds)
{
Console.WriteLine($"Running inline PowerShell countdown script for {seconds} seconds");
string inlineScript = $@"
Write-Host 'Starting countdown for {seconds} seconds'
for ($i = {seconds}; $i -gt 0; $i--) {{
Write-Host \"$i seconds remaining...\"
Start-Sleep -Seconds 1
}}
Write-Host 'Countdown complete!'
";
try
{
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = $"-ExecutionPolicy Bypass -Command \"{inlineScript}\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = false
};
using (Process process = new Process())
{
process.StartInfo = startInfo;
process.OutputDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
Console.WriteLine(e.Data);
};
process.ErrorDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
Console.WriteLine($"ERROR: {e.Data}");
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
Console.WriteLine($"Script completed with exit code: {process.ExitCode}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error executing PowerShell script: {ex.Message}");
}
}
}
}

- I defined a PowerShell script inline, using string interpolation to build the countdown logic dynamically.
ProcessStartInfo configures how the process will run:
- UseShellExecute = false: needed to redirect output.
- RedirectStandardOutput and RedirectStandardError: lets us capture and log what the script prints.
- CreateNoWindow = false: allows you to see the script’s window, if one opens.
We hook into OutputDataReceived and ErrorDataReceived to print live output from PowerShell.
WaitForExit() makes sure our app waits for the script to finish before continuing.
Note: This pattern works for any executable, not just PowerShell!


Setting this to false is essential when you want to redirect output or error streams.

Without process.WaitForExit(), your application may exit or continue before the external command finishes.

Using this ensures output is processed line-by-line and doesn't hang waiting for the full buffer.

If your script gets complex, consider writing it to a .ps1 file instead of embedding it.

Use with care, especially in production.


For instance, this is my case. I have to wait a long operation, based on git.
startInfo.FileName = "git";
startInfo.Arguments = "clone ";

startInfo.FileName = "docker";
startInfo.Arguments = "run --rm hello-world";

Use with database backups, codegen, secret rotation, etc.

Great for running test runners or diagnostic tools.

I created a Curated RSS Feed Bundle for Web Developers — a hand-picked OPML file of the best dev blogs and websites on the internet.


Источник: