4

How can I get PowerShell to understand this type of thing:

Robocopy.exe | Find.exe "Started"

The old command processor gave a result, but I'm confused about how to do this in PowerShell:

&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

2
  • If the .exe is not designed to accept piping you cannot.. Commented Oct 7, 2013 at 9:19
  • Find.exe is designed to accept piping, as is lame.exe. Any error I get is from Powershell not understanding what I mean.. You can try the Find example in cmd.exe Commented Oct 7, 2013 at 9:22

3 Answers 3

4

tl;dr

# 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"

  • 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.
  • 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 to findstr.exe can be used, as shown above.

Sign up to request clarification or add additional context in comments.

Comments

3

@Jobbo: cmd and PowerShell are two different shells. Mixing them is sometimes possible but as you realized from Shay's answer, it won't get you too far. However, you may be asking the wrong question here.

Most of the time, the problem you are trying to solve like piping to find.exe are not even necessary. You do have equivalent of find.exe, in fact more powerful version, in Powershell: select-string

You can always run a command and assign results to a variable.

$results = Robocopy c:\temp\a1 c:\temp\a2 /MIR

Results are going to be STRING type, and you have many tools to slice and dice it.

PS > $results |select-string "started"

  Started : Monday, October 07, 2013 8:15:50 PM

Comments

2

Invoke it via cmd:

PS> cmd /c 'Robocopy.exe | Find.exe "Started"'

4 Comments

This works, but if my commands have many parameters passed to them (which they do), then would I lose the benefits of passing parameters in an array, like @(,,,)? Is there any way to do this without involving cmd.exe?
This works, but is a nightmare to read: &cmd @("/c",@('robocopy','|','find "Started"')) Is it really a trade off between building it all up as one string and nesting arrays?
I know the feeling... I would write commands longer than one line in a cmd file and execute it instead.
wow that's really ragged. I wonder if I can make use of piping to Out-Host or something? I discovered I can get Robocopy's output into a Variable using Tee-Object, but then its a matter of piping a variable into an external command like: $out | &find.exe which seems just as impossible

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.