Python技术迷

用 Python 远程控制 Windows 服务器,太好用了!

我先说个场景啊,你肯定有共鸣的那种。

那天晚上快十一点多了,我在公司楼下啃着冷掉的炸鸡,运维那边微信狂响: “东哥,那个 Windows 服务器又得重启一下,顺便发个补丁脚本,能帮忙远程搞下不?”

我心里当时就只有一个念头——我要用 Python 把这些破事全自动化了,再也不想登远程桌面点来点去了,你们是不是也有这种时刻 😂

下面我就边聊边给你把这套东西理一遍,都是我自己踩坑之后沉淀出来的,代码用 Python,尽量说人话,你照着抄基本就能跑起来。

先说最实用的那一个:WinRM + pywinrm

Windows 自带一个远程管理通道叫 WinRM,你可以理解成“给 PowerShell 开了个远程入口”,Python 这边就用 pywinrm 去连它,特别顺手。

1)Windows 服务器上先开个门

在目标 Windows 上(管理员 PowerShell)执行几行:

# 开启远程管理
Enable-PSRemoting -Force

# 如果只在内网用,可以先允许 HTTP(5985端口)
winrm quickconfig

# 可选:放开防火墙(按需来)
# netsh advfirewall firewall add rule name="WinRM 5985" dir=in action=allow protocol=TCP localport=5985

如果你们安全要求比较高,建议配 HTTPS(5986)和证书,但那一套稍微麻烦点,后面你习惯了再折腾。

2)Python 端用 pywinrm 连过去

先安装依赖:

pip install pywinrm

然后最简单的远程执行命令就这样:

import winrm

session = winrm.Protocol(
    endpoint='http://192.168.1.100:5985/wsman',
    transport='ntlm',
    username=r'DOMAIN\\admin_user',
    password='Str0ng_Passw0rd!',
)

shell_id = session.open_shell()

command_id = session.run_command(shell_id, 'ipconfig', ['/all'])
stdout, stderr, rc = session.get_command_output(shell_id, command_id)

print('退出码:', rc)
print('标准输出:\n', stdout.decode('gbk', errors='ignore'))
print('错误输出:\n', stderr.decode('gbk', errors='ignore'))

session.close_shell(shell_id)

几个小点你记一下就好:

  • endpoint 就是那台 Windows 的地址+端口
  • 域账户记得 DOMAIN\\user 这种写法
  • 中文一般是 gbk,不然会乱码

顺手说一句,我之前写数据库那篇文章的时候也吐槽过默认配置这事儿,很多“默认值”在生产上都挺坑人,WinRM 这玩意儿默认也挺保守的,超时、并发数都可以根据场景调一调。

我一般会封装个小工具类,不然每次写 session 太啰嗦

随手给你丢个能直接用的版本,你可以根据自己需要改改:

import winrm
from typing import List, Tuple, Optional

classWindowsRemote:
def__init__(
        self,
        host: str,
        username: str,
        password: str,
        port: int = 5985,
        use_https: bool = False,
        domain: Optional[str] = None,
    )
:

        self.host = host
        self.port = port
        self.use_https = use_https

if domain and'\\'notin username:
            username = rf"{domain}\\{username}"

        scheme = 'https'if use_https else'http'
        endpoint = f'{scheme}://{host}:{port}/wsman'

        self._protocol = winrm.Protocol(
            endpoint=endpoint,
            transport='ntlm',
            username=username,
            password=password,
            server_cert_validation='ignore'if use_https else'ignore',
        )

        self._shell_id = self._protocol.open_shell()

defrun_cmd(self, command: str, args: Optional[List[str]] = None) -> Tuple[int, str, str]:
        args = args or []
        cmd_id = self._protocol.run_command(self._shell_id, command, args)
        stdout, stderr, rc = self._protocol.get_command_output(self._shell_id, cmd_id)

# 粗暴处理下编码问题
        out = stdout.decode('gbk', errors='ignore') or stdout.decode('utf-8', errors='ignore')
        err = stderr.decode('gbk', errors='ignore') or stderr.decode('utf-8', errors='ignore')

return rc, out, err

defrun_ps(self, script: str) -> Tuple[int, str, str]:
        cmd_id = self._protocol.run_command(self._shell_id, 'powershell', ['-NoProfile', '-NonInteractive', '-'])
        self._protocol.send_input(self._shell_id, cmd_id, script.encode('utf-8'))
        stdout, stderr, rc = self._protocol.get_command_output(self._shell_id, cmd_id)

        out = stdout.decode('utf-8', errors='ignore')
        err = stderr.decode('utf-8', errors='ignore')

return rc, out, err

defclose(self):
if self._shell_id:
            self._protocol.close_shell(self._shell_id)
            self._shell_id = None

def__del__(self):
try:
            self.close()
except Exception:
pass

用的时候就很舒服:

if __name__ == '__main__':
    client = WindowsRemote(
        host='192.168.1.100',
        username='admin',
        password='YourPassword123',
        domain='MYDOMAIN',  # 没域就传 None
    )

# 1. 来个最简单的
    rc, out, err = client.run_cmd('hostname')
    print('hostname =>', out.strip())

# 2. 跑一段 PowerShell
    ps = r"""
    Get-Service | Where-Object {$_.Status -eq 'Running'} | Select-Object -First 5 | Format-Table -AutoSize
    """

    rc, out, err = client.run_ps(ps)
    print(out)

    client.close()

有了这个类,什么重启服务、拉日志、执行脚本,就都变成一行一行的 Python 调用,爽得很。

那文件怎么弄?上传下载总得有吧

纯 pywinrm 其实是没有现成“传文件接口”的,但你绕一下就好:用 PowerShell 把文件内容做 Base64 发过去再写回磁盘。

Python 这边这么搞:

import base64
from pathlib import Path

defupload_file(client: WindowsRemote, local_path: str, remote_path: str):
    content = Path(local_path).read_bytes()
    b64 = base64.b64encode(content).decode('ascii')

    ps = f"""
    $bytes = [System.Convert]::FromBase64String("{b64}");
    [System.IO.File]::WriteAllBytes("{remote_path}", $bytes);
    """

    rc, out, err = client.run_ps(ps)
if rc != 0:
raise RuntimeError(f'上传失败: {err}')

下载反过来:

defdownload_file(client: WindowsRemote, remote_path: str, local_path: str):
    ps = f"""
    $bytes = [System.IO.File]::ReadAllBytes("{remote_path}");
    $b64 = [System.Convert]::ToBase64String($bytes);
    Write-Output $b64;
    """

    rc, out, err = client.run_ps(ps)
if rc != 0:
raise RuntimeError(f'下载失败: {err}')

    data = base64.b64decode(out.strip())
    Path(local_path).write_bytes(data)

这个方案的好处就是:只要能执行 PowerShell,你就能传文件,不用额外开放 SMB 共享端口,对安全、运维都友好一点。

顺口聊聊:你可能并不只有 WinRM 这一条路

有的人问我,“东哥,我们机器上已经开了 SSH,要不直接用 paramiko 得了?”也行,其实 Windows 10 / 2019 以后可以装 OpenSSH Server,你如果习惯 Linux 那套,其实这更顺手。

大概是这样:

# Windows Powershell 安装 OpenSSH Server(有的版本已经自带)
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Start-Service sshd
Set-Service -Name sshd -StartupType 'Automatic'

Python 这边:

import paramiko

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(
    hostname='192.168.1.100',
    port=22,
    username='Administrator',
    password='YourPassword123',
)

stdin, stdout, stderr = ssh.exec_command('whoami')
print(stdout.read().decode())

# 传文件
sftp = ssh.open_sftp()
sftp.put('local.txt', r'C:\\temp\\remote.txt')
sftp.close()

ssh.close()

SSH 这套的优势在于:生态成熟,很多公司安全规范里本来就要求统一走 SSH,这时候你就可以把 Windows 当成“有点别扭的 Linux”来用。

不过呢,跟我之前吐槽 SpringBoot 默认配置很坑一样,SSH 默认的一些超时、连接数,也要自己调调,不然一上生产,线程池和连接池那堆问题照样会找你麻烦。

再说一个稍微“偏底层”的:WMI / pywin32 控控制服务和进程

如果你需要的是那种:查某个服务状态、杀进程、看 CPU/内存,WMI 用起来还挺顺滑的,就是 API 命名有点怪。

安装:

pip install wmi pywin32

然后:

import wmi

conn = wmi.WMI(
    computer="192.168.1.100",
    user=r"MYDOMAIN\\admin",
    password="YourPassword123"
)

# 查询正在运行的进程前几条
for process in conn.Win32_Process()[:5]:
    print(process.ProcessId, process.Name)

# 操作服务
for svc in conn.Win32_Service(Name="Spooler"):
    print('Print Spooler 状态:', svc.State)
# 重启服务
    result, = svc.StopService()
    print('Stop result:', result)

WMI 最大的优点就是对 Windows 世界非常“本地化”:什么事件日志、系统信息、硬件状态,全都有对应的类。但缺点也明显:API 又长又丑,文档看起来头皮发麻,有点像你在抓 TCP 包时看偏移量那种感觉……

安全这块你一定要上点心,不然真的是自己给自己挖坑

我见过好几次事故都是这种: 代码里把 Windows 管理员密码明文写死,后来项目 Git 仓库一泄露,整个内网的服务器跟自助餐一样…

做几件小事就能好很多:

  1. 不要把密码写死在代码里 用环境变量或配置文件 + 加密,例如:

    import os

    PASSWORD = os.getenv('WIN_ADMIN_PWD')

  2. 给远程账户最小权限 不要上来就用 Domain Admin,能单机本地管理员就先单机,能只管某些服务就只给那块权限。

  3. 日志里不要乱打敏感信息 比如你只记命令和返回码,不要把完整连接字符串、密码、Token 打出来。

  4. 超时、重试、并发要控一下 不然脚本一写不好,跟个 DDoS 工具似的狂怼你自己的服务器…

最后我一般会把这些东西糅到一个“小运维脚本”里

比如给产品环境的 Windows 服务器做这些动作:

  • 批量拉最新发布包
  • 关服务 → 发包 → 清缓存 → 开服务
  • 验证进程和端口是不是正常起来了
  • 不对劲就自动回滚

思路其实就三步:

  1. 用一个 servers.yaml 维护所有机器和角色(web、job、file 等)
  2. 用上面那个 WindowsRemote 类做所有远程执行 & 传文件
  3. 给每个“动作”写成一个 Python 函数,按顺序调

你搞一遍之后,以后别人跟你说“东哥,帮我重启一下那几台 Windows”,你就可以一边喝奶茶一边敲一句:

python deploy_windows.py --env=prod --action=restart-iis

人还坐在工位上,服务器已经在机房里乖乖听话了,这种感觉…你懂的 😏

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

🔥虎哥私藏精品🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB