从Python

时间:2017-11-03 11:01:30

标签: python python-2.7 powershell

此主题的所有先前帖子都涉及其用例的特定挑战。我认为让一篇帖子只处理从Python运行PowerShell脚本的最干净的方法并询问是否有人有比我发现的更好的解决方案是有用的。

似乎普遍接受的解决方案是绕过PowerShell试图在命令中解释不同的控制字符与使用文件时提供的Powershell命令的方式不同:

ps = 'powershell.exe -noprofile'
pscommand = 'Invoke-Command -ComputerName serverx -ScriptBlock {cmd.exe \
/c "dir /b C:\}'
psfile = open(pscmdfile.ps1, 'w')
psfile.write(pscommand)
psfile.close()
full_command_string = ps + ' pscmdfile.ps1'
process = subprocess.Popen(full_command_string , shell=True, \
stdout=subprocess.PIPE, stderr=subprocess.PIPE)

当你的python代码每次调用它时都需要更改Powershell命令的参数时,你最终会编写并删除很多临时文件,以便subprocess.Popen运行。它工作得很好,但它不必要,也不是很干净。能够整理并希望得到我可以对我找到的解决方案所做的任何改进的建议真的很好。

不是将文件写入包含PS命令的磁盘,而是使用io模块创建虚拟文件。假设“日期”和“服务器”字符串作为包含此代码的循环或函数的一部分被输入,当然不包括导入:

import subprocess
import io
from string import Template
raw_shellcmd = 'powershell.exe -noprofile '

- 填充服务器和日期变量的循环开始 -

raw_pslistcmd = r'Invoke-Command -ComputerName $server -ScriptBlock ' \
        r'{cmd.exe /c "dir /b C:\folder\$date"}'

pslistcmd_template = Template(raw_pslistcmd)
pslistcmd = pslistcmd_template.substitute(server=server, date=date)
virtualfilepslistcommand = io.BytesIO(pslistcmd)
shellcmd = raw_shellcmd + virtualfilepslistcommand.read()

process = subprocess.Popen(shellcmd, shell=True, stdout=subprocess.PIPE, \
stderr=subprocess.PIPE)

- 循环结束 -

2 个答案:

答案 0 :(得分:2)

可以说最好的方法是使用powershell.exe -Command而不是将PowerShell命令写入文件:

pscommand = 'Invoke-Command ...'
process = subprocess.Popen(['powershell.exe', '-NoProfile', '-Command', '"&{' + pscommand + '}"'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

确保pscommand字符串中的双引号已正确转义。

请注意,shell=True仅在某些边缘情况下是必需的,不应在您的方案中使用。来自documentation

  

shell=True的Windows上, COMSPEC 环境变量指定默认shell。您需要在Windows上指定shell=True的唯一时间是您希望执行的命令内置于shell中(例如目录复制)。您不需要shell=True来运行批处理文件或基于控制台的可执行文件。

答案 1 :(得分:0)

为此花了很多时间。

我认为从python运行powershell命令对许多人来说可能没有意义,尤其是专门在Windows环境中工作的人。相对于powershell,python具有许多明显的优点,因此能够在python中执行所有业务逻辑,然后在远程服务器上有选择地执行powershell的能力确实很棒。

我现在已经完成了对“ winrmcntl”模块的一些改进,不幸的是,由于公司政策的原因,我无法共享该模块,但这是我对任何想做类似事情的人的建议。如果您直接在目标框中的PS中键入内容,则该模块应将未修改的PS命令或脚本块作为运行时的输入。一些技巧:

  • 为避免权限困难,请确保用户正在运行python脚本,因此请确保该用户通过process.poweropen.exe运行。Popen是对您调用命令的Windows框具有正确权限的用户。我们使用具有Windows vms的企业调度程序作为代理,python代码将驻留在该调度程序上。
  • 您有时会很少但仍然会从powershell领域获得奇怪的深奥例外,如果它们与我特别看到的那个特别的时间类似,Microsoft会稍作努力,让您做耗时的应用程序堆栈追踪。这不仅耗时,而且很难解决,因为它占用大量资源,并且您不知道下次何时会发生异常。我认为,解析异常的输出并重试x次(如果某些文本出现在这些异常中)要好得多,而且容易得多。我在winrmcntl模块中保留了一个字符串列表,该模块当前包含一个字符串。
  • 如果您不想在遍历python-> windows-> powershell-> powershell堆栈时“按摩” powershell命令,以使其在目标框上按预期工作,这是我最一致的方法我们发现,是将您的一个衬板和脚本块都写到一个ps_buffer.ps1文件中,然后将其输入到源代码框中的powershell中,以便每个process.popen看起来完全相同,但是ps_buffer.ps1的内容随每次执行而变化。 / p>

    powershell.exe ps_buffer.ps1

  • 为使python代码保持整洁,将json文件或类似文件中的powershell one衬里列表以及要运行的脚本块的指针保存到静态文件中非常好。您将json文件作为有序的dict加载,并根据您正在执行的命令循环执行。

  • 不能夸大其词,尽可能尝试使用PS的最新稳定版本,但除此之外,必须在客户端和服务器上使用同一版本。

“脚本块”和“服务器”是提供给该模块或功能的值

import subprocess
from string import Template

scriptblock = 'Get-ChildItem' #or a PS scriptblock as elaborate as you need   
server = 'serverx'

psbufferfile = os.path.join(tempdir, 'pscmdbufferfile_{}.ps1'.format(server))
fullshellcmd = 'powershell.exe {}'.format(psbufferfile)

raw_pscommad = 'Invoke-Command -ComputerName $server -ScriptBlock {$scriptblock}'
pscmd_template = Template(raw_pscommand)
pscmd = pscmd_template.substitute(server=server, scriptblock=scriptblock)

try:
    with open(psbufferfile, 'w') as psbf:
    psbf.writelines(pscmd)
....

try:
    process = subprocess.Popen(fullshellcmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    output, error = process.communicate()
....