在Python脚本中运行PowerShell脚本,如何让Python在PowerShell脚本运行时打印输出

46

我正在编写一个Python脚本,根据不同的条件运行PowerShell脚本来帮助我自动从Windows XP迁移到Windows 7。PowerShell脚本会输出自己的结果,告诉用户正在发生什么。我想将PowerShell脚本的输出作为Python脚本的输出打印出来。我查看了一些相关问题,但它们似乎对我没用。最初我尝试使用

import subprocess
subprocess.call(["C:\Users\gu2124\Desktop\helloworld.ps1"])

在这里建议使用运行Python脚本中的PowerShell函数,但我发现这会等待程序先执行而不输出结果,所以我找到了需要使用subprocess.Popen(),如此处所建议使用Popen在Python中执行PowerShell脚本,如何获取PowerShell脚本的输出并更新到网页上?,因此我尝试了以下操作:

import subprocess
subprocess.Popen(["C:\Users\gu2124\Desktop\helloworld.ps1"], stdout=sys.stdout)

我遇到了这个错误

Traceback (most recent call last):
  File "C:\Users\gu2124\Desktop\pstest.py", line 5, in <module>
    subprocess.Popen(["C:\Users\gu2124\Desktop\helloworld.py1"], stdout=sys.stdout)
  File "C:\Python27\lib\subprocess.py", line 701, in __init__
    errread, errwrite), to_close = self._get_handles(stdin, stdout, stderr)
  File "C:\Python27\lib\subprocess.py", line 848, in _get_handles
    c2pwrite = msvcrt.get_osfhandle(stdout.fileno())
  File "<string>", line 523, in __getattr__
  File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\netref.py", line 150, in __getattr__
    return syncreq(self, consts.HANDLE_GETATTR, name)
  File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\netref.py", line 71, in syncreq
    return conn.sync_request(handler, oid, *args)
  File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\protocol.py", line 434, in sync_request
    raise obj

AttributeError: DebugOutput instance has no attribute 'fileno'

我不完全确定这是什么意思,但我在阅读了此文AttributeError: StringIO实例没有属性'fileno'后,我认为是因为我错误地处理了stdout。我继续查找并找到了这个为什么我的Python子进程代码无法工作?,答案中说要使用stdout=subprocess.PIPE,所以我尝试了这个。

import subprocess
subprocess.Popen(["C:\Users\gu2124\Desktop\helloworld.ps1"], stdout=subprocess.PIPE)

我的代码没有输出结果,最后我看到了这个http://www.pythonforbeginners.com/os/subprocess-for-system-administrators,然后将代码改成了这样

import subprocess
p = subprocess.Popen(["powershell","C:\Users\gu2124\Desktop\helloworld.ps1"], stdout=subprocess.PIPE)
print p.communicate

我认为问题可能是因为我最初尝试从命令行运行PowerShell脚本,所以我必须先打开PowerShell。当我直接在命令行中输入这些命令时,它能够正常工作,但是当我通过Python脚本运行它时,会出现此问题

<bound method Popen.communicate of <subprocess.Popen object at 0x00000000026E4A90>>

这可能是一种改进,但并不是我期望的“Hello world”示例。我不知道下一步应该尝试什么来使它起作用。如果有任何帮助,将不胜感激。

另外,如果需要使用我的PowerShell脚本,请在此处查看。

$strString = "Hello World"
write-host $strString

function ftest{
$test = "Test"
write-host $test
}

编辑:我尝试升级到Python 3.3,就像第一个答案建议的那样,但我仍然无法让它正常工作。 我使用命令 p = subprocess.Popen(['powershell.exe',"C:\\Users\\gu2124\\Desktop\\helloworld.ps1"],stdout=sys.stdout) 确定文件存在,但是出现以下错误:

Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    p = subprocess.Popen(['powershell.exe', "C:\\Users\\gu2124\\Desktop\\helloworld.ps1"], stdout=sys.stdout)
  File "C:\Python27\lib\subprocess.py", line 701, in __init__
    errread, errwrite), to_close = self._get_handles(stdin, stdout, stderr)
  File "C:\Python27\lib\subprocess.py", line 848, in _get_handles
    c2pwrite = msvcrt.get_osfhandle(stdout.fileno())
UnsupportedOperation: fileno 

当你写print p.communicate时,你打印的是方法对象,而不是运行该方法的结果。你需要将其更改为print p.communciate()才能获得想要的结果。 - NuclearPeon
你还没有导入sys模块,这会导致调用sys.stdout失败。UnsupportedOperation表示你的操作系统不支持fileno()。如果你看这里:http://forums.devshed.com/python-programming-11/python27-pyserial-windows-broken-select-fileno-827240.html,它提到了一个文档,其中提到fileno()仅适用于unix。如果`subprocess.Popen()`对你不起作用,你可以尝试使用`os.system()`,但它已经被弃用了。 - NuclearPeon
4个回答

60
  1. 确保您可以运行PowerShell脚本(默认情况下被禁用)。很可能您已经完成了这一步骤。 http://technet.microsoft.com/en-us/library/ee176949.aspx

Set-ExecutionPolicy RemoteSigned
  • 在你的powershell脚本中运行这个Python脚本helloworld.py

  • # -*- coding: iso-8859-1 -*-
    import subprocess, sys
    
    p = subprocess.Popen(["powershell.exe", 
                  "C:\\Users\\USER\\Desktop\\helloworld.ps1"], 
                  stdout=sys.stdout)
    p.communicate()
    

    这段代码是基于Python3.4(或任何3.x系列解释器)编写的,虽然它也应该可以在Python2.x系列上工作。

    C:\Users\MacEwin\Desktop>python helloworld.py
    Hello World
    

    8
    这里的难点是非PS和Py专家很难理解的:如果你有许多PowerShell命令行参数(或者非常长/复杂的一行命令),你必须使用数组调用shell,其中每个项目都是单独的参数。否则,你可能要花费数小时来调试为什么输出不符合预期。 - not2qubit

    6

    我没有安装Python 2.7,但是在Python 3.3中,使用Popen并将stdout设置为sys.stdout完全可以正常工作。不过需要先转义路径中的反斜杠。

    >>> <b>import subprocess</b>
    >>> <b>import sys</b>
    >>> <b>p = subprocess.Popen(['powershell.exe', 'C:\\Temp\\test.ps1'], stdout=sys.stdout)</b>
    >>> Hello World
    _

    我尝试按照你的建议更改了我的代码并升级到Python 3.3,但它仍然无法工作...我编辑了问题以反映我的新输出和代码。 - user3282276

    6

    除了之前的回答,我还有一些建议可以使你的代码更具可移植性。

    你可以使用以下方法,而不是全局设置 ExecutionPolicyRemoteSigned(这会带来一些安全问题),仅设置Python脚本创建的PowerShell实例:

    import subprocess, sys    
    p = subprocess.Popen('powershell.exe -ExecutionPolicy RemoteSigned -file "hello world.ps1"', stdout=sys.stdout)
    p.communicate()
    

    注意引用符,这允许您的PowerShell脚本路径/文件名包含空格。
    此外,如上例所示,您可以使用相对路径来调用您的PowerShell脚本。该路径相对于您的Python工作区目录。

    使用相对路径在以管理员身份运行Python时无法正常工作。 - Billy Cao
    我认为即使在使用管理员权限运行Python时,它也可以正常工作。您只需要确保您的Python实例使用正确的工作区目录即可。 - Stefan

    2
    这是我从Popen获取输出的方法。
    p = subprocess.Popen(["powershell","C:\Users\gu2124\Desktop\helloworld.ps1"], stdout=subprocess.PIPE)
    p_out, p_err = p.communicate()
    
    print(p_out)
    

    从Popen.communicate()文档中得知,该函数返回一个元组(stdout_data, stderr_data)。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接