1

If I pass an IF statement through PowerShell's Invoke-Expression, the command appears to be running and completing, but then it appears that the output is being evaluated as a new command instead of being returned to PowerShell. Three examples:

  1. Invoke-Expression 'echo "hi"' (No IF statement)

Normal Output: hi

  1. Invoke-Expression 'cmd /c IF exist C:\Windows (echo "hi")'

Error on Output: 'hi' is not recognized as an internal or external command, operable program or batch file.

  1. Invoke-Expression 'cmd /c IF exist C:\Windows (query user)'

Error on Output:

'" USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME"' is not recognized as an internal or external command, operable program or batch file.

What's the best way to run a command-line IF statement from PowerShell and be able to read its output? I tried Start-Process but cannot figure out for the life of me how to read its output. Tried a System.Diagnostics.ProcessStartInfo object copied from another StackOverflow post, but no luck there either.

Because people are bound to ask: The reason why I'm passing this through cmd in the first place is because this entire code block needs to be passed through Invoke-Command to a remote machine and cmd has folder/file access to computers on its network while PowerShell does not.

justanotherguy
  • 506
  • 2
  • 4
  • 18
  • 2
    Why are you using `Invoke-Expression` to run a CMD command from PowerShell? No, scratch that. Why are you using `Invoke-Expression` at all? – Ansgar Wiechers Sep 09 '19 at 17:21
  • 3
    The best way to run command line utilities from PowerShell is to run them from PowerShell. – Ansgar Wiechers Sep 09 '19 at 17:22
  • 2
    Here's why `Invoke-Expression` is [bad](https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/why-invoke-expression-is-evil) – techguy1029 Sep 09 '19 at 17:24
  • @Ansgar Wiechers I already gave a brief-version of the answer to your first question at the bottom of my post. As for, "Why are you using Invoke-Expression at all?" Do you mind contributing some actual advice? Tell me what I should be using! I'm a PowerShell noob. – justanotherguy Sep 09 '19 at 17:24
  • 3
    Perhaps surprisingly, PowerShell is an actual shell. Like any other shell it allows you to run external commands directly. Meaning that all you have to do is `$output = query user`, or `$output = if (Test-Path 'C:\Windows') {query user}` if you want to execute the external command under a particular condition. I recommend you go find a PowerShell tutorial right now. – Ansgar Wiechers Sep 09 '19 at 17:38
  • 2
    As for your explanation why you think you need `Invoke-Expression`: none of those reasons apply. PowerShell can do all of what you described without `Invoke-Expression`. – Ansgar Wiechers Sep 09 '19 at 17:41
  • @AnsgarWiechers Powershell is failing me on this one thing that Invoke-Expression seems to be able to do: This works on Server A: `Test-Path '\\Server_C\c$\Windows'`. But this does *not* work on Server B talking to Server A `Invoke-Command -ComputerName $Server_A -Credential $cred -ScriptBlock { Test-Path '\\Server_C\c$\Windows' }`. Server B can use Test-Path on Server A but can't ask Server A to Test-Path on Server C. Invoke-Expression can get through, but obviously has issues itself. – justanotherguy Sep 09 '19 at 17:53

1 Answers1

2

Your immediate problem is unrelated to the use of Invoke-Expression, which should generally be avoided:

cmd /c IF exist C:\Windows (echo "hi")  # WRONG

is interpreted by PowerShell first, up front, and (echo "hi") is the same as (Write-Output "hi"), which PowerShell expands (interpolates) to the command's output, a string with content hi.

The - broken - command line that cmd exe ends up seeing is the following, which explains the error message:

cmd /c IF exist C:\Windows hi

For an overview of how PowerShell parses unquoted command-line arguments, see this answer.


There are several ways to fix that problem, appropriate in different scenarios:

# Single-quoting - passed as-is.
cmd /c 'IF exist C:\Windows (echo "hi")'

# Double-quoting - PowerShell would still expand $-prefixed tokens up front.
cmd /c "IF exist C:\Windows (echo `"hi`")"

#`# The stop-parsing symbol, --%, prevents PowerShell from parsing subsequent arguments, 
# with the exception of cmd-style environment-variable references (%FOO%)
cmd /c --% IF exist C:\Windows (echo "hi")

Now, with Invoke-Expression you'd have to add escape those quotes due to having to specify them as part of a string, but, as mentioned in the comments, there is rarely a need for Invoke-Expression, and it is neither needed here, nor would I expect it to help with the "double hop" authentication problem you describe in a comment.

To address the latter, try this answer, which uses explicitly passed credentials to establish an auxiliary drive mapping on the remote machine.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Perfect answer... You explained my syntax question, addressed my core problem, and gave me some best practice advice which is exactly what I needed. So believe it or not... with that syntax fix, the `Invoke-Expression` actually *does* get around the multihop problem: `Invoke-Expression 'cmd /c --% IF exist \\Server_C\c$\Window (echo hi)'` <- This works now. I already started on a different multihop workaround, but like the example you linked to and am going to test that out. THANK YOU SO MUCH! – justanotherguy Sep 09 '19 at 21:15
  • I'm glad to hear it was helpful, @justanotherguy. I'm puzzled that `Invoke-Expression` would make a difference here, though; are you saying that if you use direct invocation along the lines of `Invoke-Command -ComputerName ServerB { cmd /c 'IF exist \\Server_C\c$\Window (echo hi)' }` it does _not_ work? If it does, I encourage you to use _that_. Avoiding `Invoke-Expression` is a good habit to form. – mklement0 Sep 09 '19 at 21:20