1

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?

Mofi
  • 46,139
  • 17
  • 80
  • 143
Ooker
  • 1,969
  • 4
  • 28
  • 58
  • 6
    Powershell files aren't like .bat files - you have to feed the .ps1 file into the powershell executable. From an OS level, you don't run a ps1 file, you just run powershell.exe with that script as an argument. If you change your code so that it runs powershell with your ps1 file as an arg it should work. – John Dec 05 '22 at 17:35
  • do you mean to use `proc.StartInfo.FileName = @"powershell.exe 'absolute/path/to/test.ps1'";`? Weirdly it says `The system cannot find the file specified.'` Do you know why is that? – Ooker Dec 05 '22 at 17:43
  • 2
    FileName should be just the path to the executable you want, so it'll be the location of powershell.exe on your computer. Check out this answer for an example: https://stackoverflow.com/a/30846513/6457336 – John Dec 05 '22 at 17:48
  • There are two executables for powershell 1) ps.exe (command line) 2) powershell.exe (interactive) They can be found in following folder : C:\Windows\System32\WindowsPowerShell – jdweng Dec 05 '22 at 18:15
  • 1
    @jdweng, there is no `ps.exe`. For Windows PowerShell, the single CLI - both for interactive and automated invocations is `powershell.exe` (`C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`). For PowerShell (Core), the cross-platform, install-on-demand PowerShell edition, the single CLI is `pwsh.exe` (Windows) / `pwsh` (Unix-like platforms). While there are standard installation directories if you use the official installers, you're free to install it anywhere, possibly multiple versions side by side. – mklement0 Dec 05 '22 at 18:35
  • @John I see. If you make an answer I'll accept it – Ooker Dec 06 '22 at 04:41

1 Answers1

1

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.

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;
}
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • why are batch files still the only script-type file that are treated as executable files? I thought that Microsoft is trying to replace CMD with PowerShell, so at least ps1 script should also be treated as executable files? – Ooker Dec 07 '22 at 15:19
  • 1
    @Ooker, that's a good question, but it seems that Microsoft went the opposite direction with PowerShell right from the beginning: you also cannot run `.ps1` files by opening (double-clicking) them in File Explorer / on the desktop, even though that works with batch files. If this were to change, also at the WinAPI level - my guess is it that it won't - an additional conceptual challenge is that now there are _two_ PowerShell editions, so the system would have to default to the edition that is _guaranteed_ to be there, which is the _legacy, maintenance-only_ edition, namely _Windows PowerShell_ – mklement0 Dec 07 '22 at 15:27
  • Is there any reason for not making ps1 files as executables at the beginning? – Ooker Dec 09 '22 at 02:49
  • 1
    @Ooker, I presume the reason is the same as not allowing execution of `.ps1` script files by default even from _inside_ PowerShell (on client editions of Windows), based on [execution policies](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Execution_Policies) (which your CLI call is also subject to). That is, Microsoft apparently deems execution of `.ps1` script files _risky_, requiring deliberate action to enable it. – mklement0 Dec 09 '22 at 20:21
  • Then how is batch safer? – Ooker Dec 10 '22 at 04:12
  • 1
    @Ooker, it fundamentally isn't, and I can't speak to what caused the change of heart (PowerShell came along much, much later than `cmd.exe`). I can only _speculate_ that perhaps they thought that PowerShell's advanced capabilities make it _easier_ to do nefarious things. – mklement0 Dec 10 '22 at 04:23