1

I executed the following three powershell commands. The first two commands returned no results, and the third command returned results. The main difference between the three commands is the use of the wait argument and parentheses.

PS C:\Users> Start-Process -FilePath 'msiexec' -ArgumentList '/I C:\Users\Downloads\Everything-1.4.1.1015.x64.msi -quiet' -PassThru | Foreach-Object -Process { $_.exitcode }

PS C:\Users> Start-Process -FilePath 'msiexec' -ArgumentList '/I C:\Users\Downloads\Everything-1.4.1.1015.x64.msi -quiet' -PassThru -wait | Foreach-Object -Process { $_.exitcode }

PS C:\Users> (Start-Process -FilePath 'msiexec' -ArgumentList '/I C:\Users\Downloads\Everything-1.4.1.1015.x64.msi -quiet' -PassThru -Wait) | Foreach-Object -Process { $_.exitcode }

1619

I test another two commands, and the difference between them was the use of parentheses. Both commands returned results no matter with parentheses.

PS C:\Users> Start-Process -FilePath 'msiexec' -ArgumentList '/I C:\Users\Downloads\Everything-1.4.1.1015.x64.msi -quiet' -PassThru | Foreach-Object -Process { $_.id }

22980

PS C:\Users> (Start-Process -FilePath 'msiexec' -ArgumentList '/I C:\Users\Downloads\Everything-1.4.1.1015.x64.msi -quiet' -PassThru -Wait) | Foreach-Object -Process { $_.id }

8064

Thanks for explanation for -wait parameter. I still confused with the difference caused by parentheses. Hope for more replies.

mklement0
  • 382,024
  • 64
  • 607
  • 775
jiligulu
  • 187
  • 6
  • 2
    The `ExitCode` property cannot be populated _before the process exits_ - `-Wait` causes `Start-Process` to wait until the target process has exited before returning – Mathias R. Jessen Jan 13 '22 at 02:42
  • command 2 and 3 both use -Wait, but command 2 returned no results. – jiligulu Jan 13 '22 at 03:03
  • Please review the code you've posted - only the 3rd one has the `-Wait` switch – Mathias R. Jessen Jan 13 '22 at 03:06
  • Sorry about posting the wrong command. I corrected it. – jiligulu Jan 13 '22 at 03:10
  • How does new edit relate to the question being asked before? It's not clear what exactly you want to know – Santiago Squarzon Jan 13 '22 at 03:14
  • 1
    I understand the difference caused by the wait argument, but I also want to know the difference caused by parentheses. – jiligulu Jan 13 '22 at 03:29
  • I see what you mean, I believe the parentheses are holding the object in memory and once the `ExitCode` is recorded it passes that object to the `ForEach-Object`, may be wrong tho – Santiago Squarzon Jan 13 '22 at 03:36
  • 1
    Good point, @Santiago: `(...)` forces waiting for `Start-Process` _as a whole_ to exit, and (with `-Wait`) only _that_ guarantees that the process has truly exited; by contrast, without `(...)` the newly launched process is emitted _to the pipeline right away_, before it has terminated, and that's why `ForEach-Object` cannot yet access `.ExitCode`. – mklement0 Jan 13 '22 at 03:47

1 Answers1

3

The main difference between the three commands is the use of the -Wait argument and parentheses

To build on Mathias R. Jessen's helpful comment:

It is primarily the use of Start-Process's -Wait switch that is required:

  • Without -Wait, Start-Process runs asynchronously.

  • Without -PassThru, Start-Process produces no output.

While -PassThru makes Start-Process output a System.Diagnostics.Process instance representing the newly launched process, unless -Wait is also present that instance's .ExitCode property has no value yet, because the launched process typically hasn't exited yet.

Additionally, parentheses ((...)) are required too, because Start-Process emits the System.Diagnostics.Process instance representing the newly launched process to the pipeline (as received by ForEach-Object) right away, and then waits for the process to exit. By using (...), the grouping operator, you're forcing a wait for Start-Process itself to exit, at which point the Process' instance .ExitCode property is available, thanks to -Wait.

In general, wrapping a command in (...) forces collecting its output in full, up front - which includes waiting for it to exit - before the results are passed through the pipeline (as opposed to the streaming (one-by-on output) behavior that is the default, which happens while the command is still running).

Therefore, the following works - but see the bottom section for a simpler alternative:

# Note: With (...), you could also pipe the output to ForEach-Object, as in
#       your question, but given that there's by definition only *one* 
#       output object, that is unnecessary.
(
  Start-Process -PassThru -Wait -FilePath 'msiexec' -ArgumentList '/i C:\Users\Downloads\Everything-1.4.1.1015.x64.msi -quiet' 
).ExitCode

It follows from the above that using two separate statements would work too (given that any statement runs to completion before executing the next):

$process = Start-Process -PassThru -Wait -FilePath 'msiexec' -ArgumentList '/i C:\Users\Downloads\Everything-1.4.1.1015.x64.msi -quiet' 
$process.ExitCode 

msiexec.exe is unusual in that:

  • it is a GUI(-subsystem) executable (as opposed to a console(-subsystem) executable), which therefore - even when invoked directly - runs asynchronously.

  • yet it reports a meaningful process exit code that the caller may be interested in, requiring the caller to wait for its exit (termination) in order to determine this exit code.

As an aside: For invoking console applications, Start-Process is not the right tool in general, except in unusual scenarios - see this answer.


An simpler alternative to using msiexec with Start-Process -PassThru -Wait is to use direct invocation via cmd /c, which ensures both (a) synchronous invocation and (b) that PowerShell reflects msiexec's exit code in its automatic $LASTEXITCODE variable:

cmd /c 'msiexec /i C:\Users\Downloads\Everything-1.4.1.1015.x64.msi -quiet'
$LASTEXITCODE  # output misexec's exit code

Note: If the msiexec command line needs to include PowerShell variable values, pass an expandable (double-quoted) string ("...") to cmd /c instead and - as with verbatim (single-quoted) string ('...') strings - use embedded double quoting around embedded arguments, as necessary.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • when I want to get exitcode, I must use parentheses. when I want to get id, Whether or not I use parentheses has no effect. – jiligulu Jan 13 '22 at 03:31
  • 1
    @jiligulu The `Id` is assigned on process _startup_, so that's immediately available regardless of whether you wait for the process to exit or not. It's a simple question of timing. – Mathias R. Jessen Jan 13 '22 at 03:36
  • @jiligulu, you are correct: the`(...)` are _also_ needed in order to get an `.ExitCode` value - please see my update. However, as @Mathias points out, _other_ properties of the `Process` instance, such as `.Id` are available right away. – mklement0 Jan 13 '22 at 03:37