In this script, if I use a batch file, it works:
private async void cmdRunBatchFile2_Click(object sender, EventArgs e)
{
cmdRunBatchFile2.Enabled = false;
await Task.Run(() => {
var proc = new Process();
proc.StartInfo.FileName = @"test.bat";
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.CreateNoWindow = true;
if (proc.Start())
{
//do something
}
proc.WaitForExit();
});
cmdRunBatchFile2.Enabled = true;
}
However if I change it to test.ps1
, it returns this error: System.ComponentModel.Win32Exception: 'An error occurred trying to start process 'test.ps1' with working directory XYZ. The specified executable is not a valid application for this OS platform.'
After reading .Net Core 2.0 Process.Start throws "The specified executable is not a valid application for this OS platform", I then blindly try adding
proc.StartInfo.UseShellExecute = true;
This yields another error: System.InvalidOperationException: 'The Process object must have the UseShellExecute property set to false in order to redirect IO streams.'
Do you know why is that?
CodePudding user response:
First things first: An alternative, more efficient and flexible way to execute PowerShell code from a .NET executable is to use the PowerShell SDK, which enables in-process execution with full .NET type support - see this answer for an example.
Batch files (.bat
, .cmd
files) have special status on Windows: They are the only script-type files that the Windows API - which underlies the .NET APIs - treats as if they were directly executable, binary files, by implicitly passing such files to cmd.exe /c
for execution.
All other script files, which by definition require a scripting engine (shell) to execute, can not be executed this way, and instead you must specify their associated scripting engine / shell CLI as the executable in the .FileName
property of the System.Diagnostics.ProcessStartInfo
class, followed by the appropriate engine / shell-specific command-line parameter that requests execution of a script file, if necessary, and the script file's path itself.
- Note:
- If you use
.UseShellExecute = true
, you'll lose the ability to capture the launched process' output and to control its window (creation) style. - With
.UseShellExecute = true
, you can specify a script file directly in.FileName
, but there's no guarantee that it will execute, given that is the default GUI shell operation that is then performed (which never runs in the current console window), which for script files often means opening them for editing.
- If you use
In order to execute a .ps1
script via powershell.exe
, the Windows PowerShell CLI, use powershell.exe -file
.
Therefore:
private async void cmdRunBatchFile2_Click(object sender, EventArgs e)
{
cmdRunPs1File.Enabled = false;
await Task.Run(() =>
{
var proc = new Process();
// Specify the PowerShell CLI as the executable.
proc.StartInfo.FileName = @"powershell.exe";
// Specify arguments, i.e. the .ps1 script to invoke, via -File.
// Note: This assumes that 'test.ps1` is located in the process' current dir.
// Consider placing '-NoProfile' before '-File', to suppress
// profile loading, both for speed and a predictable execution environment.
proc.Start.Info.Arguments = @"-File test.ps1";
// !! .UseShellExecute must be false in order to be able to
// !! capture output in memory and to use .CreateNoWindow = true
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.CreateNoWindow = true;
if (proc.Start())
{
//do something
}
proc.WaitForExit();
});
cmdRunPs1File.Enabled = true;
}