Search code examples
powershellcmdinvoke-command

Passing a cmd-line IF statement through Invoke-Expression breaks on output


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.


Solution

  • 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.