Question:
How can I get PowerShell to understand this type of thing:
1 2 |
Robocopy.exe | Find.exe "Started" |
The old command processor gave a result, but I’m confused about how to do this in PowerShell:
1 2 3 4 5 6 |
&robocopy | find.exe "Started" #error &robocopy | find.exe @("Started") #error &robocopy @("|", "find.exe","Started") #error &robocopy | &find @("Started") #error &(robocopy | find "Started") #error |
Essentially I want to pipe the output of one external command into another external command. In reality I’ll be calling flac.exe and piping it into lame.exe to convert FLAC to MP3.
Cheers
Answer:
tl;dr
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# Note the nested quoting. CAVEAT: May break in the future. robocopy.exe | find.exe '"Started"' # Alternative. CAVEAT: doesn't support *variable references* after --% robocopy.exe | find.exe --% "Started" # *If available*, use PowerShell's equivalent of an external program. # In lieu of `findstr.exe`, you can use Select-String (whose built-in alias is scs): # Note: Outputs are *objects* describing the matching lines. # To get just the lines, pipe to | % ToString # or - in PowerShell 7+ _ use -Raw robocopy.exe | sls Started |
For an explanation, read on.
PowerShell does support piping to and from external programs.
The problem here is one of parameter parsing and passing: find.exe
has the curious requirement that its search term must be enclosed in literal double quotes.
In cmd.exe
, simple double-quoting is sufficient: find.exe "Started"
By contrast, PowerShell by default pre-parses parameters before passing them on and strips enclosing quotes if the verbatim argument value doesn’t contain spaces, so that find.exe
sees only Started
, without the double quotes, resulting in an error.
There are three ways to solve this:
- PS v3+ (only an option if your parameters are only literals and/or environment variables):
--%
, the stop-parsing symbol, tells PowerShell to pass the rest of the command line as-is to the target program (reference environment variables, if any, cmd-style (%<var>%
)):
robocopy.exe | find.exe --% "Started"
- This answer details the limitations of
--%
.
- This answer details the limitations of
- PS v2 too, or if you need to use PowerShell variables in the parameters: apply an outer layer of PowerShell quoting (PowerShell will strip the single quotes and pass the contents of the string as-is to
find.exe
, with enclosing double quotes intact):
robocopy.exe | find.exe '"Started"'
- Caveat: It is only due to broken behavior that this technique works. If this behavior gets fixed (the fix may require opt-in), the above won’t work anymore, because PowerShell would then pass
""Started""
behind the scenes, which breaks the call – see this answer for more information.
- Caveat: It is only due to broken behavior that this technique works. If this behavior gets fixed (the fix may require opt-in), the above won’t work anymore, because PowerShell would then pass
- If an analogous PowerShell command is available, use it, which avoids all quoting problems. In this case, the
Select-String
cmdlet, PowerShell’s more powershell analog tofindstr.exe
can be used, as shown above.