如何包装Windows交互式控制台程序以实现自动化

时间:2012-08-03 05:25:13

标签: python subprocess pipe stdin getch

我在Windows中有一个交互式控制台程序。我需要按下像'e''c'这样的按键到cmd窗口。它方便人工操作,但程序自动化非常困难。

现在,我想包装控制台程序,以便在python等其他程序中更容易操作。

但是,控制台程序使用'getch()'来获取键盘输入,这不是标准输入。所以,我不能简单地将密钥发送到标准输入。

有没有人遇到过这个问题? 感谢。

    class ThreadWorker(threading.Thread):
        def __init__(self, callable, *args, **kwargs):
            super(ThreadWorker, self).__init__()
            self.callable = callable
            self.args = args
            self.kwargs = kwargs
            self.setDaemon(True)

        def run(self):
            try:
                self.callable(*self.args, **self.kwargs)
            except Exception, e:
                print e

        def start(self):
            global running
            running = True
            threading.Thread.start(self)

    def console_presskey(char):
        proc.stdin.write(char)
        proc.stdin.flush()

    def start():
        def worker(pipe):
            while running:
                line = pipe.readline()
                if line == '': 
                    break
                else:
                    print line,

        proc = Popen("something.exe",stdout=PIPE,stdin=PIPE)

        stdout_worker = ThreadWorker(worker, proc.stdout)
        stderr_worker = ThreadWorker(worker, proc.stderr)
        stdout_worker.start()
        stderr_worker.start()


    if __name__ == "__main__":
        start()
        sleep(2)
        console_presskey('e')
        sleep(2)
        console_presskey('c')

编辑:

最后,我使用win32 SendMessage函数来完成任务。我分叉了一个新的子进程,然后隐藏它,得到它的hWnd和pid。

这是代码:

import threading
import re
import os
from subprocess import Popen, PIPE,STDOUT,CREATE_NEW_CONSOLE,STARTUPINFO,STARTF_USESHOWWINDOW,SW_HIDE
from time import sleep
import win32process,win32con,win32gui,win32api

TargetPower = 'N/A'
Mode = 'N/A'
Channel = 'N/A'
con_stdin = ''
con_stdout = ''
stdout_worker = ''
stdin_woker = ''
running = False
proc = ''
Console_hwnd = ''
#status = 'NotStarted' # or Running 
class ThreadWorker(threading.Thread):
    def __init__(self, callable, *args, **kwargs):
        super(ThreadWorker, self).__init__()
        self.callable = callable
        self.args = args
        self.kwargs = kwargs
        self.setDaemon(True)

    def run(self):
        try:
            self.callable(*self.args, **self.kwargs)
        except Exception, e:
            print e

    def start(self):
        global running
        running = True
        threading.Thread.start(self)
def get_hwnds_for_pid (pid):
    def callback (hwnd, hwnds):
        #if win32gui.IsWindowVisible(hwnd) and win32gui.IsWindowEnabled(hwnd):
        _, found_pid = win32process.GetWindowThreadProcessId(hwnd)
        #print hwnd
        if found_pid == pid:
            hwnds.append(hwnd)
        return True
    hwnds = []
    win32gui.EnumWindows(callback, hwnds)
    #if hwnds == []:
        #raise 
    return hwnds

def sendkey(char):
    global Console_hwnd
    hwnd = Console_hwnd[0]
    code = ord(char)
    win32api.SendMessage(hwnd, win32con.WM_CHAR, code, 0)
    print '[*]',char,'Pressed.'

    #Another Keypress example. only works with keycode
    #win32api.PostMessage(hwnd, win32con.WM_KEYDOWN, win32con.VK_F9, 0)
    #print char,code,"down"
    #win32api.PostMessage(hwnd, win32con.WM_KEYUP, code, 0)
    #print char,code,"up"
def sendesc():
    global Console_hwnd
    hwnd = Console_hwnd[0]
    win32api.PostMessage(hwnd, win32con.WM_KEYDOWN, win32con.VK_ESCAPE, 0)
    print '[*]',"Esc down"
    win32api.PostMessage(hwnd, win32con.WM_KEYUP, win32con.VK_ESCAPE, 0)
    print '[*]',"Esc up"

def start():
    def worker(pipe):
        global TargetPower
        global Mode
        global Channel
        global running
        while running:
            line = pipe.readline()
            if line == '':
                break
            elif line.startswith('|') or line.startswith('==='):
                pass
            elif line.startswith("Operating in"):
                info = line
                for i in range(7):
                    info += pipe.readline()
                    #print 'ART> '+info,
                try:
                    TargetPower = eval(re.search(r'(Output|Target) (power|Power) = .{4}',info).group(0).split('=')[1])
                    #27.0
                    Mode = re.search(r'gOffRate = (.+?),',info).group(1).lstrip().rstrip()
                    #6MBps
                    Channel = re.search(r'channel ([\d.]+GHz),',info).group(1)
                    #2.412GHz
                except Exception,e:
                    TargetPower = 'N/A'
                    Mode = 'N/A'
                    Channel = 'N/A'
                    print e
            elif line =='\r\n':
                print '>',
            else:
                print 'ART>'+line,
        print 'worker done.'
    global proc
    si = STARTUPINFO()
    si.dwFlags |= STARTF_USESHOWWINDOW
    si.wShowWindow = SW_HIDE
    #proc = Popen("art.bat",stdout=PIPE,creationflags=CREATE_NEW_CONSOLE) #qt works!
    proc = Popen("art.bat",stdout=PIPE,creationflags=CREATE_NEW_CONSOLE,startupinfo=si)
    #proc = Popen("art.bat",stdout=PIPE,startupinfo=si)  #hidden
    #proc = Popen("cmd")  #for test
    sleep(2)
    print '[*] pid: ',proc.pid

    global Console_hwnd
    Console_hwnd = get_hwnds_for_pid(proc.pid)
    print '[*] hwnd:',Console_hwnd[0]

    global stdout_worker
    global stderr_worker
    stdout_worker = ThreadWorker(worker, proc.stdout)
    stderr_worker = ThreadWorker(worker, proc.stderr)
    stdout_worker.start()
    stderr_worker.start()


def stop():
    global stdout_worker
    global stderr_worker
    global running
    print 'stop'
    global proc
    sendesc()
    sendesc()
    sendesc()
    Popen("taskkill /F /T /PID %i"%proc.pid , shell=True)
    try:
        running = False
        TargetPower = 'N/A'
        Mode = 'N/A'
        Channel = 'N/A'
    except Exception,e:
        print e

if __name__ == "__main__":
    start()
    sleep(1)
    sendkey('e')
    sleep(1)
    sendkey('c')
    sleep(10)
    stop()
    while True:
        pass

2 个答案:

答案 0 :(得分:0)

据我所知,getch() 使用标准输入。问题是应用程序确实收到了那个输入,但你没有收到它的输出?这是一个常见问题,因为如果输出不是针对终端,则输出通常会被缓冲。你可以调整程序以在每一行之后刷新它的输出吗?

我看到你的python代码有很多问题:你没有为stderr创建一个管道,proc变量似乎是start方法的本地。

您可能希望暂时保留从python进程继承的子进程输出。这应该可以抑制缓冲并排除ThreadWorker中的任何错误。

答案 1 :(得分:0)

您需要做的就是:

使用新行中的每一步写一个文件:

例如:

第1步 第2步 第3步

然后像这样调用程序:

program.exe< input.txt中

nuff说!!