Home > Blockchain >  powershell Pipe Operators
powershell Pipe Operators

Time:01-13

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.

CodePudding user response:

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.

  •  Tags:  
  • Related