Benjamin Hodgson
Benjamin Hodgson

Reputation: 44634

Can Powershell read code from stdin?

I'm trying to run a Powershell subprocess from Python. I need to send Powershell code from Python to the child process. I've got this far:

import subprocess
import time

args = ["powershell", "-NoProfile", "-InputFormat None", "-NonInteractive"]

startTime = time.time()
process = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

process.stdin.write("Write-Host 'FINISHED';".encode("utf-8"))

result = ''
while 'FINISHED' not in result:
    result += process.stdout.read(32).decode('utf-8')
    if time.time() > startTime + 5:
        raise TimeoutError(result)
print(result)

This times out, because nothing ever gets written to stdout. I think the Write-Host cmdlet never gets executed. Even the simple bash/Cygwin code echo "Write-Host 'FINISHED';" | powershell doesn't seem to do the job.

For comparison, sending the code block using the -Command flag works correctly.

How can I convince Powershell to run the code which I'm sending to stdin?

Upvotes: 4

Views: 5343

Answers (2)

RaJa
RaJa

Reputation: 1567

I had trouble with a similar task, but I was able to solve it.

First my example code:

import subprocess

args = ["powershell.exe", "-Command", r"-"]
process = subprocess.Popen(args, stdin = subprocess.PIPE, stdout =   subprocess.PIPE)

process.stdin.write(b"$data = Get-ChildItem C:\\temp\r\n")
process.stdin.write(b"Write-Host 'Finished 1st command'\r\n")
process.stdin.write(b"$data | Export-Clixml -Path c:\\temp\state.xml\r\n")
process.stdin.write(b"Write-Host 'Finished 2nd command'\r\n")

output = process.communicate()[0]

print(output.decode("utf-8"))
print("done")

The main issue was the correct argument list args. It is required to start the powershell with the -Command-flag, followed by "-" as indicated by Jan-Philipp.

Another mystery was the end-of-line character that is required to get the stuff executed. \r\n works quite well.

Getting the output of the Powershell is still an issue. But if you don't care about realtime, you can collect the output after finishing all executions by calling

output = process.communicate()[0]

However, the active Powershell will be terminated afterwards.

Upvotes: 4

Dr. Jan-Philip Gehrcke
Dr. Jan-Philip Gehrcke

Reputation: 35776

There a couple of things you can consider:

  1. Invoke PowerShell in a mode where you provide it with a script file which it should execute. Write this script file prior to calling the subprocess. Use the -File <FilePath> parameter for PowerShell (cf. the docs)

  2. If you really want to go with the stdin technique, you might be missing a newline character after the command. If this does not help, you might need to send another control character that tells PowerShell that input EOF is reached. You definitely need to consult the PowerShell docs for finding out how to 'terminate' commands on stdin. One thing you definitely need is the -Command - arguments: The value of Command can be "-", a string. or a script block. If the value of Command is "-", the command text is read from standard input. You may also want to look at this little hack: https://stackoverflow.com/a/13877874/145400

  3. If you only want to execute one command, you can simplify your code by using out, err = subprocess.communicate(in)

Upvotes: 3

Related Questions