python - 如何在纯python中的Windows上获取父 shell 的路径?

标签 python windows shell

以下内容将返回启动当前进程的 shell 程序以及该 shell 程序的完整路径。但是,它使用了一个充满c扩展名的Python库。有时, shell 程序会启动 shell 程序,等等。我只是在寻找作为 shell 程序的“最新祖先”进程。
我该如何使用纯Python做到这一点? (即没有c扩展名,使用windll.kernel32之类的东西就可以了-当然,在某些时候要获取流程信息代码将必须访问特定于平台的 native 代码,只需要将其嵌入python标准库中,不需要编译c)
Shellingham at the moment can't do this

from typing import Tuple, List

import os

import psutil

SHELL_NAMES = {
    'sh', 'bash', 'dash', 'ash',    # Bourne.
    'csh', 'tcsh',                  # C.
    'ksh', 'zsh', 'fish',           # Common alternatives.
    'cmd', 'powershell', 'pwsh',    # Microsoft.
    'elvish', 'xonsh',              # More exotic.
}

def find_shell_for_windows() -> Tuple[str,str]:
    names_paths:List[Tuple[str,str]]=[]
    current_process = psutil.Process(os.getppid())
    process_name, process_path = current_process.name(), current_process.exe()
    names_paths.append((process_name, process_path))
    for parent in current_process.parents():
        names_paths.append((parent.name(), parent.exe()))
    for n,p in names_paths:
        if n.lower() in SHELL_NAMES or n.lower().replace(".exe","") in SHELL_NAMES:
            return n,p
    return ["",""]

if __name__ == '__main__':
    print(find_shell_for_windows())

最佳答案

问题在于寻找一种方法来遍历祖先进程列表,并找到第一个可执行文件与硬编码名称列表中的某个项目匹配的可执行文件,希望它将成为用户的“ shell ”。在我回答之前,这是做这件事绝对愚蠢和无用的一些原因:

  • 您正在寻找的可执行文件可能会与您期望的名称不同。在对问题的评论中,我举了一个示例,说明了Bash可执行文件shab.exe的命名,该问询者使用众所周知的医生笑话的妙语(“好吧,那就不要那样做”)将其驳回,因为用户试图“隐藏”他们的 shell 。但是,即使没有任何“恶意”意图“隐藏”用户部分的内容,也会发生这种情况。例如,用户可能安装了具有不同可执行名称(bash-4.00.exegitbash.exe等)的多个Bash版本,以便测试其脚本是否与所有脚本兼容。您是否要枚举代码中每个可能的名称?还是要将所有可执行文件的名称都与sh匹配,并用交叉手指指称没有误报?
  • 相反,just because an executable has the name you expect, it doesn’t mean it will have the behaviour you expect。在Windows上更是如此,在Windows上,实际上并没有一组标准化的可执行文件名称,这些名称应始终具有特定的规定行为。如果您不加选择地将诸如cmdbashfishxonsh之类的程序总称为'shell',则尤其如此:这些程序在其自己的命令行和各自的脚本语言中接受不同的语法。除非您要做的就是启动 shell 程序以供用户进行交互,否则您将要寻找特定类型的 shell 程序-无论是POSIX兼容 shell 程序,还是DOS shell 程序,例如cmd或其他完全–为了利用其特殊行为。仅仅知道它是一个“ shell ”并没有告诉您任何有用的信息。而且请不要忘记并非所有的Shell甚至都不是命令行-毕竟Windows Explorer是Shell。
  • 即使可执行文件名称排列完美,硬编码列表也无法穷举。询问者的列表已省略 tcmd tclsh 。我听说有些疯狂的人将Python本身用作 shell –您是谁来阻止他们?如果出现新的 shell ,则必须将其作为列表中的另一个条目添加;希望还有人记得当时的位置。
  • 尽管为了争辩而说,我们只对命令行shell感兴趣,而忽略其他所有内容。如果脚本是从本身由Bash启动的资源管理器进程启动的,该怎么办?该脚本将忽略资源管理器进程,并选择Bash作为“ shell ”,即使显然是资源管理器而不是Bash启动了脚本。这是正确的还是期望的?我认为答案远非显而易见。

  • 但是,如果上述方法不能阻止您完成这项徒劳的任务(或者您可能想为更明智的目的而做类似的事情),那么无论如何,您可以通过以下方式完成这项荒谬的事情:
    import ctypes, ctypes.wintypes, contextlib
    
    k32 = ctypes.windll.kernel32
    
    INVALID_HANDLE_VALUE = ctypes.wintypes.HANDLE(-1).value
    ERROR_NO_MORE_FILES = 18
    ERROR_INSUFFICIENT_BUFFER = 122
    TH32CS_SNAPPROCESS = 2
    PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
    
    def _check_handle(error_val=0):
        def check(ret, func, args):
            if ret == error_val:
                raise ctypes.WinError()
            return ret
        return check
    
    def _check_expected(expected):
        def check(ret, func, args):
            if ret:
                return True
            code = ctypes.GetLastError()
            if code == expected:
                return False
            raise ctypes.WinError(code)
        return check
    
    class ProcessEntry32(ctypes.Structure):
        _fields_ = (
            ('dwSize'             , ctypes.wintypes.DWORD),
            ('cntUsage'           , ctypes.wintypes.DWORD),
            ('th32ProcessID'      , ctypes.wintypes.DWORD),
            ('th32DefaultHeapID'  , ctypes.POINTER(ctypes.wintypes.ULONG)),
            ('th32ModuleID'       , ctypes.wintypes.DWORD),
            ('cntThreads'         , ctypes.wintypes.DWORD),
            ('th32ParentProcessID', ctypes.wintypes.DWORD),
            ('pcPriClassBase'     , ctypes.wintypes.LONG),
            ('dwFlags'            , ctypes.wintypes.DWORD),
            ('szExeFile'          , ctypes.wintypes.CHAR * ctypes.wintypes.MAX_PATH),
        )
    
    k32.CloseHandle.argtypes = \
        (ctypes.wintypes.HANDLE,)
    k32.CloseHandle.restype = ctypes.wintypes.BOOL
    
    k32.CreateToolhelp32Snapshot.argtypes = \
        (ctypes.wintypes.DWORD, ctypes.wintypes.DWORD)
    k32.CreateToolhelp32Snapshot.restype = ctypes.wintypes.HANDLE
    k32.CreateToolhelp32Snapshot.errcheck = _check_handle(INVALID_HANDLE_VALUE)
    
    k32.Process32First.argtypes = \
        (ctypes.wintypes.HANDLE, ctypes.POINTER(ProcessEntry32))
    k32.Process32First.restype = ctypes.wintypes.BOOL
    k32.Process32First.errcheck = _check_expected(ERROR_NO_MORE_FILES)
    
    k32.Process32Next.argtypes = \
        (ctypes.wintypes.HANDLE, ctypes.POINTER(ProcessEntry32))
    k32.Process32Next.restype = ctypes.wintypes.BOOL
    k32.Process32Next.errcheck = _check_expected(ERROR_NO_MORE_FILES)
    
    k32.GetCurrentProcessId.argtypes = ()
    k32.GetCurrentProcessId.restype = ctypes.wintypes.DWORD
    
    k32.OpenProcess.argtypes = \
        (ctypes.wintypes.DWORD, ctypes.wintypes.BOOL, ctypes.wintypes.DWORD)
    k32.OpenProcess.restype = ctypes.wintypes.HANDLE
    k32.OpenProcess.errcheck = _check_handle(INVALID_HANDLE_VALUE)
    
    k32.QueryFullProcessImageNameW.argtypes = \
        (ctypes.wintypes.HANDLE, ctypes.wintypes.DWORD, ctypes.wintypes.LPWSTR, ctypes.wintypes.PDWORD)
    k32.QueryFullProcessImageNameW.restype = ctypes.wintypes.BOOL
    k32.QueryFullProcessImageNameW.errcheck = _check_expected(ERROR_INSUFFICIENT_BUFFER)
    
    @contextlib.contextmanager
    def Win32Handle(handle):
        try:
            yield handle
        finally:
            k32.CloseHandle(handle)
    
    def enum_processes():
        with Win32Handle(k32.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)) as snap:
            entry = ProcessEntry32()
            entry.dwSize = ctypes.sizeof(entry)
            ret = k32.Process32First(snap, entry)
            while ret:
                yield entry
                ret = k32.Process32Next(snap, entry)
    
    def get_full_path(proch):
        size = ctypes.wintypes.DWORD(ctypes.wintypes.MAX_PATH)
        while True:
            path_buff = ctypes.create_unicode_buffer('', size.value)
            if k32.QueryFullProcessImageNameW(proch, 0, path_buff, size):
                return path_buff.value
            size.value *= 2
    
    SHELLS = frozenset((
        b'sh.exe', b'bash.exe', b'dash.exe', b'ash.exe',
        b'csh.exe', b'tcsh.exe',
        b'ksh.exe', b'zsh.exe', b'fish.exe',
        b'cmd.exe', b'powershell.exe', b'pwsh.exe',
        b'elvish.exe', b'xonsh.exe',
    ))
    
    def find_shell_for_windows():
        proc_map = {
            proc.th32ProcessID: (proc.th32ParentProcessID, proc.szExeFile)
            for proc in enum_processes()
        }
    
        pid = proc_map[k32.GetCurrentProcessId()][0]
        proc = proc_map[pid]
        while proc:
            ppid, name = proc
            if name in SHELLS:
                with Win32Handle(k32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid)) as proch:
                    return get_full_path(proch)
            pid, proc = ppid, proc_map.get(ppid)
    
    if __name__ = '__main__':
        print(find_shell_for_windows())
    
    上面的代码仅使用ctypes即可完成要求的操作,并且可以在Windows Vista及更高版本(在Windows 7上使用Python 3.8测试)上运行。对于较旧的Windows版本,可能必须更改某些Win32调用(最重要的是QueryFullProcessImageNameW)。

    关于python - 如何在纯python中的Windows上获取父 shell 的路径?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65902925/

    相关文章:

    python - 使用 Django 的 Q 查询过滤多对多关系

    python - 如何在 python 服务器中的某个时间间隔后调用函数?

    python - Selenium:FirefoxProfile 异常无法加载配置文件

    python - Keras ImageDataGenerator 中的增强

    python - 如何在 Windows 上访问文件的属性?

    windows - vim 显示带@符号的文件内容

    windows - 在 Perl for win32 中获取 CPU ID 序列号

    bash - 从 shell 脚本执行 Maven 任务并获取错误代码

    shell - 打印 shell 命令的执行时间

    linux - Bash 脚本对文件进行排序,然后转储到文件中